명령어 포맷 인코딩(Instruction Format Encoding)

개요

개발자는 개발할 때 개발 언어를 통해 아이디어를 구체화합니다.

리버서는 만들어진 프로그램을 디스어셈블러를 통해 어셈블러로 번역해서 보거나 디컴파일러로 C/C++ 코드로 보기도 합니다.

그렇다면 CPU는 개발자가 컴파일러로 만든 최종 결과물 PE파일이나 ELF파일에서 명령어 단위를 어떻게 해석하는 것일까요?

Intel Manual에서는 CPU가 이를 해석하는 방식에 대해 설명하고 있습니다.

https://cdrdv2.intel.com/v1/dl/getContent/671110

Chapter2의 Instruction Format에 대해 설명한 글을 여러 자료와 함께 정리해보도록 하겠습니다.

Instruction Format

명령어 포맷은 위와 같이 순차적으로 나열되어 있으며, ModR/M부터 SIB, Diplacement, Immediate는 옵션입니다.

Prefix는 주로 Rex Prefix가 사용되어 명령어의 크기, 피연산자, 주소를 확장하는데 사용됩니다.

Opcode는 명령어로 1~3바이트가 사용되며 ModR/M의 reg 필드에서 추가로 3비트를 더 사용하기도 합니다.

ModR/M은 diplacement 사용 여부를 알려주는 Mode와 reg, r/m 필드를 가지고 있습니다.

SIB는 Scale, Index, Base로 이뤄져 있으며 이를 통해 배열과 같은 순차적인 요소에 쉽게 접근할 수 있습니다.

Diplacement는 메모리나 주소에 접근할 때 추가로 더해지는 값으로, 구조체의 멤버 변수에 접근할 때 사용합니다.

Immediate는 상수 값으로, 데이터의 값이 상수일 때 주로 사용됩니다.

Prefix

Prefix는 Opcode가 최대 3바이트+3비트의 제약을 갖고 있기에 이를 더 확장시키고자 사용하는 것입니다.

예를 들어 Opcode에서 mov 명령어는 32비트 형식의 Mov opcode까지만을 제공하는데, 여기서 x64비트 형식에

명령어를 추가하려면 위의 제약인 3바이트+3비트 크기를 넘어가게 됩니다.

Windows는 16비트부터 시작해서 32비트, 64비트로 단계별로 넘어오게 되었으며, 이들이 다 호환될 수 있도록 설계되었습니다.

따라서 예전부터 정해진 Opcode의 크기를 바꾼다는 것은 이전 버전의 윈도우와의 호환 체계의 문제가 발생하기 때문에 Prefix라는 개념을 도입하기 시작했습니다.

이런 방식을 통해 윈도우는 Opcode 자체만으로 명령어를 이해할 수도 있고, Prefix를 통해 Opcode를 추가적으로 해석할 수 있게 됩니다.

Intel Manual에서 크게 설명하는 Legacy Prefix는 총 4가지 그룹입니다.

Group 1 : Lock(0xF0) and Repeat Prefixes (0xF2, 0xF3)

Group 2 : Segment override Prefixes

Group 3 : Operand-size override Prefix (0x66)

Group 4 : Address-size override Prefix (0x67)

여기서 주로 사용되는 Prefix는 Group1과 Group3입니다.

Opcode

ModR/M

SIB

Diplacement

Immediate

함수 호출 규약 ( cdelc, stdcall, fastcall, thiscall)

함수 호출 규약이란?

함수 호출 규약이란 서브 함수를 호출하는 측(호출자)과 호출되는 측(피호출자) 중 누가 해당 함수의 자산을 관리할 지 미리 정의한 방식입니다.

int __cdecl Add(int a, int b);
int __stdcall Add(int a, int b);
int __fastcall Add(int a, int b);
void __thiscall MyClass::Func();

Cdecl ( C Declarartion )

초창기 C 컴파일러가 사용하던 기본 함수 호출 규약입니다.

Cdecl는 편의상 “씨덱”이라고 읽으며, 호출자가 피호출자의 스택을 정리합니다.

이러한 방식은 서브함수에 가변 인자를 전달할 수 있게 해주며, 대표적인 함수는 C언의 printf 함수가 있습니다.

Cdecl은 함수 인자를 오른쪽부터 push하며, 함수가 마무리 된 이후 호출자 측에서 아래와 같은 코드를 실행합니다.

call 서브함수 
add esp, 인자 크기

Stdcall ( Standard Call )

스탠다드 콜은 WIN API에서 주로 사용합니다.

해당 함수 호출 규약은 사용할 인자 개수가 정해져 있기 때문에 고정된 형식에서만 사용 가능합니다.

StdCall은 인자 개수가 정해져 있기 때문에, 피호출자 측에서 코드를 정리합니다.

따라서 피호출자의 함수 코드 부분에서 아래와 같은 코드가 발견됩니다.

ret 인자 크기

Fastcall

패스트 콜은 레지스터를 통해 인자를 전달할 때 주로 사용합니다.

대표적으로 x86 MSVC(Microsoft Visual C++)에서 사용하며, GCC나 G++와 같은 다른 컴파일러에서

해당 옵션을 설정하여 컴파일할 시 사용할 수 있습니다.

ECX와 EDX 레지스터를 우선적으로 사용하며, 이후 인자는 스택에 넣어 전달합니다.

해당 호출 규약 또한 호출자가 사용한 스택을 정리합니다. (예 : ret 8)

Thiscall

디스 콜은 C++의 클래스 멤버함수를 호출할 때 사용하는 규약입니다.

해당 호출 규약은 멤버함수 호출 시, 첫번째 인자로 this 포인터가 전달되기 위해서 사용되는데요.

예를 들어 Class가 Person 안에 Talk라는 함수가 존재할 경우, 멤버함수는 아래와 같습니다.

Void Person::Talk(int arg1, int arg2) {}; // 개발자가 이와 같이 작성
Void Person::Talk(Person* this, int arg1, int arg2); // 아래와 같이 talk 함수에 this가 전달됨

첫 번째 함수로 해당 인스턴스의 포인터가 전달되는데 이때는 ECX를 사용합니다.

그 외 인자는 오른쪽부터 왼쪽으로 스택에 푸시되어 전달되고, 스택 정리는 피호출자에서 진행됩니다.

x64

64비트에서는 cdecl, stdcall, fastcall의 차이가 거의 사라지고 레지스터로 전달됩니다.

1 = RCX, 2 = RDX, 3 = R8, 4 = R9 , 이후는 스택으로 전달됩니다.

스택 정리는 호출자가 진행합니다.

마무리하며

x86 시절에는 함수 호출규약으로 cdecl, stdcall, fastcall, thiscall과 같이 여러 호출 규약을 써서

리버싱할 때 인자를 파악하는데 불편한 점이 있었습니다.

x86을 직접 리버싱할 때 참고 하시면 좋을 것 같습니다.

감사합니다.

Retained Mode vs Immediate Mode

Retained Mode

Retained Mode는 화면이 바뀔 때 상태 값을 객체에 저장하고 시스템이 그리는 방식입니다.

Direct 2D나 React는 이러한 방식을 통해 UI나 Scene을 수정하고 있습니다.

이 방식을 사용하면 개발자는 그리는 과정에 쓰는 에너지를 줄일 수 있습니다.

그림을 그릴 때 데이터 값을 전달만 하면 수정이 되는 방식이기 때문에 직접 그리는 함수를 호출하여

일일히 순차적으로 넣어주지 않아도 되는 장점이 있지만, 커스텀에 제약이 따릅니다.

Immediate Mode

Immediate Mode는 화면을 그릴 때 개발자가 직접 그리는 방식입니다.

Html의 Canvas나 Windows API의 직접 선을 긋거나 그림을 그리는 것이 이 방식입니다.

아주 세밀하게 그림을 그리는 것이 가능하며, 객체로 관리하는 과정이 없기 때문에 더 빠릅니다.

다만 그리는 과정을 직접 관리해야 하기 때문에, 개발 비용이 추가되며, 코드가 직관적이지 않습니다.

Retained Mode와 Immediate Mode의 큰 차이는 이 객체를 시스템이나 라이브러리가 담당하는 지,

아니면 프로그램(개발자)이 직접 담당하는 지의 차이입니다.

** 참고 (AI 참고)

https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-drawing-a-line-use

useSharedValue vs useDerivedValue

useSharedValue

useSharedValue는 애니메이션의 초기값을 설정할 때나 UI Thread에서 자주 변경되는 값을 저장하기 위한 상태입니다.

초기 애니메이션 상태나 좌표, 크기, 회전값을 저장합니다.

import { useSharedValue } from 'react-native-reanimated';

function App() {
  const sv = useSharedValue(100);

  // read a shared value
  console.log(sv.value);

  // and modify it
  sv.value += 50;
}

React에서 useState와 같은 역할을 한다고 생각할 수 있지만 큰 차이를 갖는데요.

React-Native는 JS코드를 Android나 Ios와 같은 실제 네이티브 컴포넌트로 전환하게 됩니다.

useState를 사용할 경우 setState -> React Render -> React Native Bridge/Fabric -> Native View -> 그리기 과정을 거칩니다.

하지만 useSharedValue는 기기 내부에 있는 UI Thread에서 값을 저장하고, 이를 직접 처리합니다.

sharedValue.value 변경 -> (UI Thread에서 바로 처리) -> Native View 변경 -> 화면 그리기 과정을 거칩니다.

useSharedValue는 UI Thread에서, useState는 JS Thread에서 관리하기 때문에 애니메이션과 상태 관리는 각각의 함수에서 처리하는 것이 좋습니다.

이를 통해 애니메이션의 상태 값이 운영을 담당하는 JS Thread와 분리되기 때문에 JS Thread가 바쁘더라도 애니메이션이 끊기지 않고 동작할 수 있습니다.

// 1. React가 count.value 값이 변경되어도  렌더링을 하지 않음
const count = useSharedValue(0);

<Text>{count.value}</Text>

// 2.  React가 count.value 값이 변경될 때  애니메이션을 변경함
const style = useAnimatedStyle(() => ({
  transform: [{ translateX: count.value }],
}));

useDerivedValue

useDerivedValue는 애니메이션의 값을 계산할 때 주로 사용합니다.

shared Value 값을 만들 때 useMemo처럼 계산식을 써서 새로운 값을 만드는 형태입니다.

useDerivedValue는 주로 SharedValue를 기반으로 계산식을 통해 새로운 값을 만들 때 사용합니다.

useDerivedValue는 useSharedValue와 마찬가지로 UI Thread 내부에서 실행됩니다.

const x = useSharedValue(100);

const doubled = useDerivedValue(() => {
  return x.value * 2;
});

위의 두 함수는 애니메이션을 담당하는 상태값을 관리하기 위해 사용되며, JS Thread가 아닌 UI Thread 내부에서 사용됩니다.

따라서 애니메이션 중에도 JS Thread와 관계없이 부드럽게 값을 계산할 수 있으며, 계산된 값은 다른 애니메이션 스타일이나 Worklet에서 바로 사용할 수 있습니다.

** 참고

https://docs.swmansion.com/react-native-reanimated/docs/core/useSharedValue

https://docs.swmansion.com/react-native-reanimated/docs/core/useDerivedValue

포트를 사용하고 있는 프로세스 종료하기

포트 조회

Get-NetTCPConnection -LocalPort [포트번호]

포트를 사용중인 프로세스 정보 조회

Get-Process -Id (Get-NetTCPConnection -LocalPort 3000).OwningProcess

PID를 통해 프로세스 종료하기

Stop-Process -Id [PID] -Force

워드프레스에서 메뉴 목록 수 조정하고 싶을 때

Monobase 테마를 쓰던 중 화면의 높이가 충분함에도 3개까지만 목록이 표시되지 않는 문제가 발생하였습니다.

해당 메뉴 설정 창에 들어가도 이와 관련한 설정을 변경할 수가 없어서 코드를 찾아보던 중 아래와 같은 코드를 발견하였습니다.

[설정 – 모양 – 테마 파일 편집기]

해당 PHP 코드를 분석한 결과 monobase_loop_single_sidebar 함수를 통해 리스트 목록을 만들어 뿌려준다는 것을 파악하였습니다.

서버에 접속하여 wp-content/themes/monobase로 이동한 후 아래 명령어를 통해 해당 함수를 사용하는 php파일을 찾습니다.

grep -R "function monobase_loop_single_sidebar" .
=> ./inc/template-tags.php:function monobase_loop_single_sidebar(): void {

vim으로 함수를 분석한 결과 $per_page라는 변수에 get_option으로 갖고온 값을 통해 리스트 목록 수를 조절하는 것을 확인하였습니다.

get_option함수는 wordpress가 제공해주는 함수로, DB에 있는 값을 갖고올 때 사용합니다.

따라서 데이터베이스에 설정된 값을 변경하면 됩니다.

mysql -u [아이디] -p
=> [패스워드] 입력

use wordpress;

select * from wp_options where option_name="posts_per_page"; -- 현재 선택된 갯수 확인하기

update wp_options set option_value=[원하는 갯수] where option_name="posts_per_page";

Monobase에서 메뉴에 있는 목록 갯수를 페이지 당 포스트 개수를 이용해서 표시하기 때문에

이와 관련한 설정을 찾아 변경하면 되지만, 저는 못 찾아서 서버에 있는 mysql을 통해 수정하였습니다.

추후 서버를 옮기거나 데이터베이스가 재설치될 때, 초기화 될 수 있는 점 참고 부탁드립니다.

감사합니다.