psj2867
c언어의 main 은 함수여야 할까 본문
java의 실행 구조를 궁금하다보니 jit 를 찾아봤고 jit 방식이 미리 native machine code 를 만들고 실행시키는 것을 알았다.
그럼 machine code 를 실행 임의로 실행할 수 있는 걸까
일단 실행이 가능한지 부터 알기 위해 c 언어 기준으로 살펴봤다.
해보고 싶은 것은
char bytes[] = { 1,2,3,4 };
int(*f)(void) = (int(*)(void))bytes ;
f();
byte 를 함수로 인식하고 호출하는 것이다.
우선 bytes 에 들어갈 byte 코드 부터 만들어야 했다.
위의 코드를 실행할 같은 환경에서
int f2(){
return 3;
}
이 코드르 컴파일 및 분석한다.
gcc -shared test.c -o tset.out && objdump -d test.out
00000000000010f5 :
10f5: 55 push %rbp
10f6: 48 89 e5 mov %rsp,%rbp
10f9: b8 03 00 00 00 mov $0x3,%eax
10fe: 5d pop %rbp
10ff: c3 retq
그럼 이런식의 machine code 를 얻을 수 있다!
코드만 정리하면 55 48 89 e5 b8 03 00 00 00 5d c3 이렇고 입력을 위해 바꾸면 이렇게 된다.
char bytes[] = { 0x55, 0x48, 0x89, 0xe5, 0xb8, 0x03, 0x00, 0x00, 0x00, 0x5d, 0xc3 }
최종적으로 이렇게 된다.
#include <stdio.h>
int main(){
char bytes[] = { 0x55, 0x48, 0x89, 0xe5, 0xb8, 0x03, 0x00, 0x00, 0x00, 0x5d, 0xc3 };
int(*f)(void) = (int(*)(void))bytes ;
printf("%d", f());
}
그리고 이 코드는 당연히 실패한다. 원인은 저 bytes 는 데이터 영역의 메모리에 있는데 그곳의 코드는 실행할 수 있는 권한이 없다.
코드를 어떻게 조절해서 텍스트(코드) 영역에 넣을 수도 있어 보이지만 조금 더 직관적으로 코드를 보이고 싶었다.
메모리 영역에 권한이 없으면 bytes 를 권한이 있는 메모리에 복사하고 실행시키면 된다.
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main ()
{
char code[] = { 0x55, 0x48, 0x89, 0xe5, 0xb8, 0x05, 0x00, 0x00, 0x00, 0x5d, 0xc3 };
int (*f_ptr) (void) = mmap (0, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
memcpy (f_ptr, code, sizeof(code));
__builtin___clear_cache (f_ptr, f_ptr + sizeof(f_ptr));
printf ("%d\n", f_ptr() );
}
중간에 전역으로 const 선언하면 text 영역에 들어간다는 얘기도 있었는데 컴파일러 마다 다르고 일단 테스트 환경에서는 rodata 영역에 들어갔다.
#include <stdio.h>
__attribute__((section(".text"))) const char code[] = { 0x55, 0x48, 0x89, 0xe5, 0xb8, 0x09, 0x00, 0x00, 0x00, 0x5d, 0xc3 };
int main ()
{
int (*f_ptr) (void) = (int(*)(void))code;
printf ("%d\n", f_ptr() );
}
더 찾아보니 text 영역에 들어가게 하는 방법이 있어 성공했다. 컴파일러는 attribute 이 무시됐다고는 하는데 코드는 text 영역에 들어가있고 실행도 정상적으로 되었다.
드디어 성공하는 코드가 완성이 됐다.
실행환경은 x86-64 linux 지만 아마 대부분 windows 에서도 따로 설정 안 건들이면 실행은 될 것 같다.
실제로 저 코드가 동작하는지 좀더 재밌게 보려면 10f9, (mov $0x3,%eax) 이 코드 즉 bytes[5:8] 까지 숫자(littel endian)를 바꾸면 값이 바뀌는 것을 확인할 수 있다.
그리고 드디어 제목이 나온다. 위의 코드를 실행하기 위해 자료를 찾던 중에 정말 정말 신기하고 놀라운 제목을 발견했다.
"Main is usually a function. So then when is it not?" - 메인은 일반적으로 함수입니다. 그럼 언제가 아닌가요?
메인 함수가 일반적으로 함수이고 아닐 수도 있다..?
main 함수는 꼭 함수여야 하는가
실제로 확인을 하려면 출력을 하는 것이 좋지만 번거롭고 길어지는 관계로 단순하게 main 에서 종료 코드로 잘 안 쓰이는 3을 return 하여 확인한다.
__attribute__((section(".text"))) const char main[] = {0x55, 0x48, 0x89, 0xe5, 0xb8, 0x03, 0x00, 0x00, 0x00, 0x5d, 0xc3};
컴파일 후 ide 환경에서는 대부분 종료 코드 3 을 알려줄 것이고 shell 에서는 "echo $?" 로 확인 가능하다.
그리고 놀랍게도 성공했다.
참조
http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html
'기타' 카테고리의 다른 글
elastic search 정리 (0) | 2023.01.27 |
---|---|
bash shell script 정리 (0) | 2023.01.20 |
nginx - auth_basic 간단한 구조 설명 (0) | 2022.10.18 |
언어별 GC 비교 (0) | 2022.10.07 |
공부한 것 나열 (0) | 2022.10.07 |