NASM 어셈블리(리눅스) 에서 C표준 라이브러리 함수 호출하기
글쓴이: 노력하는자 / 작성시간: 월, 2016/05/09 - 9:18오후
안녕하세요 어셈블리 공부하는 컴공입니다.
일단 제가 지금 질문하려는 내용은 제목그대로입니다
리눅스 환경에서 NASM어셈블러를 가지고 C표준 라이브러리 함수(printf, scanf등등)를 호출하는 예제를 공부하고있는데요. (어셈소스 파일은 첨부했습니다 라인수는 500줄정도.)
일단 어셈블러로 빌드 하는것 까지는 문제가 없습니다
(빌드할때 명령어)
nasm -f elf -d ELF_TYPE asm_io.asm
다만 이해가 안되는 부분이 있어서 질문드립니다
; ; Linux C doesn't put underscores on labels ; %ifdef ELF_TYPE %define _scanf scanf %define _printf printf %define _getchar getchar %define _putchar putchar %endif ; ; Watcom puts underscores at end of label ; %ifdef WATCOM %define _scanf scanf_ %define _printf printf_ %define _getchar getchar_ %define _putchar putchar_ %endif
여기서 왜 굳이 C표준 함수 심볼명인 scanf, printf 등등에 Underscore(밑줄) 을 붙여서 매크로로 재정의 한다음 사용하는지..
그리고 Watcom이라는건 무슨 환경인지.. 이게 첫번째 의문이구요
그리고 코드 세그먼트 나오는부분에 또 궁금한부분있습니다
; ; code is put in the _TEXT segment ; %ifdef OBJ_TYPE segment text public align=1 class=code use32 %else segment .text %endif global read_int, print_int, print_string, read_char global print_char, print_nl, sub_dump_regs, sub_dump_mem global sub_dump_math, sub_dump_stack extern _scanf, _printf, _getchar, _putchar
위에서 재정의한 C표준함수들을 extern 명령어로 인식시키고있는데..
어셈블러는 이놈들을(extern 선언된놈들) 빌드할때 어디서 인식하는건가요?
C언어에서는 stdio.h라는 헤더를 인클루드해서 C표준함수를 사용하잖아요?
근대 이 어셈코드를보면 아무리봐도 "printf, scanf라는 바로 ~ 이다" 하는 내용이없습니다. 그냥 위처럼 extern 선언한것뿐입니다.
그래도 빌드가 잘되더군요;
다음으로는 질문은 아니지만 정수,문자열을 출력하는 프로시저를 2개 가져와봤습니다.
print_int: enter 0,0 pusha pushf push eax push dword int_format call _printf pop ecx pop ecx popf popa leave ret print_string: enter 0,0 pusha pushf push eax push dword string_format call _printf pop ecx pop ecx popf popa leave ret
File attachments:
첨부 | 파일 크기 |
---|---|
asm_io.txt | 8.76 KB |
Forums:
어셈블리를 잘 모르지만 매크로 정의하는
어셈블리를 잘 모르지만 매크로 정의하는 이유는,
보시다시피 ELF_TYPE나 WATCOM 따라 호출해야 하는 심볼이 다릅니다.
하지만 매크로를 통해서 한 줄만 바꾸면 되도록 작성한 것 처럼 보입니다.
만약 매크로를 정의하지 않았다면 플랫폼이 바뀔 경우 소스 코드의 매 호출부분을
모두 바꿔줘야겠지요.
답변감사드립니다 덕분에 첫번째는 궁금증이
답변감사드립니다 덕분에 첫번째는 궁금증이 해결됬습니다.
여러 컴파일 환경에서 서로다른 표준라이브러리 심볼 이름들을 어셈코드상에서 동일하게 처리하기 위함이군요
[1] 첫 번째 의문 Quote:여기서 왜 굳이
[1] 첫 번째 의문
C 컴파일러의 name mangling (== name decoration) 때문일 것입니다.
( https://en.wikipedia.org/wiki/Name_mangling )
C 컴파일러는 사용자가 정의한 함수를 컴파일할 때 나름대로의 규칙으로 이름을 변형하곤 합니다.
변형 없이 그냥 컴파일해주는 경우도 있지만, 대개는 앞에 언더스코어(_)를 붙인다든지
뒤에 @를 붙인다든지 등등 이런저런 약간의 변형을 합니다.
C 표준 라이브러리 함수들도 빌드에 사용된 C 컴파일러에 따라
이런 name mangling이 일어난 상태이므로 그런 라이브러리와 링크하기 위해서는
어셈블리 코드에서 적절히 name mangling을 흉내내줘야 합니다.
(참고로, C 컴파일러의 name mangling은 애교 수준이지만,
C++ 컴파일러들은 class, namespace 등의 구분을 위해 별 짓을 다합니다
이런 복잡한 변형을 피하고 C 언어 방식의 name mangling을 쓰려면 extern "C" 지정을 합니다 )
리눅스에서는 아래와 같이 name mangling 결과를 확인할 수 있습니다.
gcc로 빌드한 glibc의 경우 name mangling이 일어나지 않았네요.
그래서, 리눅스용으로 nasm -f elf -d ELF_TYPE asm_io.asm 명령으로 어셈블하면
아래와 같이 언더스코어(_) 없는 버전으로 심볼을 재정의하고 있는 것입니다.
기본형을 언더스코어 버전으로 해 놓은 이유는 어셈블리 소스 작성자가
그것이 가장 전형적인 name mangling 형태라고 판단한 것 아닐까요?
Watcom은 C/C++ 컴파일러 가운데 하나입니다.
( https://en.wikipedia.org/wiki/Watcom_C/C%2B%2B_compiler )
asm_io.asm 소스의 주석을 보면, Watcom C 컴파일러는
name mangling도 함수 이름 뒤에 언더스코어를 붙이는 식으로 좀 특이하게 하고
함수에 대한 calling convention도 다른 컴파일러와 좀 다르게 하는 모양이네요.
[2] 두 번째 의문
아래의 샘플에서 보실 수 있듯, 굳이 stdio.h 전체를 #include 하지 않더라도
printf 함수에 대한 extern 선언 한 줄만 있으면 printf 함수를 사용할 수 있습니다.
asm_io.asm 파일에서 printf 등을 쓰기 위해 하고 있는 일도
이와 같은 것이라고 보면 혼란이 사라지죠?
두 번째 의문을 다시 읽어보니, C언어에서
두 번째 의문을 다시 읽어보니, C언어에서 printf()를 쓸 때는
extern _printf
함수호출이 제대로 이뤄지려면 파라미터 갯수/타입, 반환값이 파악되어야 하는 것 말고도,
어떤 식으로 파라미터를 넘겨주고 (스택, 레지스터, 둘을 섞은 형태, 스택은 어떻게 쓸 지...),
어떤 식으로 스택의 파라미터를 청소할지 (호출자가 청소할 지, 호출된 함수 내에서 청소할 지),
어떤 식으로 반환값을 받아올 지 (어떤 레지스터를 이용할 지 ...) 등도 합의되어야 하는데
이런 것이 calling convention으로 정의됩니다.
C 언어에서는 이런 부분을 컴파일러가 책임지기 때문에, 대상 함수에 대한 프로토타입이 요구됩니다.
프로토타입에는 파라미터/반환값 정보 뿐 아니라 caling convention도 함축적으로 담겨 있습니다.
(프로토타입에는 cdecl, stdcall, ... 식으로 calling convention 지정이 가능한데,
detail은 컴파일러 또는 ABI에 의해 결정된다고 해야 할 것 같습니다)
그에 비해 어셈블러는 이런 것을 전혀 신경쓰지 않으므로 프로토타입 선언같은 것은 필요 없고
함수의 calling convention에 맞게 코딩하는 책임은 그냥 프로그래머에게 떠넘겨집니다.
심볼 이름을 name mangling 신경써서 정확히 지정했다면,
그 함수 호출 관행에 맞게 호출코드를 짜면 됩니다.
C++이 아닌 C에서도 네임맹글링을
C++이 아닌 C에서도 네임맹글링을 수행하나요?
nm 이용해서 심볼 이름 확인했을때 한번도 이름이 바뀐걸 본 적이 없어서 질문드립니다.
MSVC는 32-bit 빌드할 경우 이름이 바뀐다고
MSVC는 32-bit 빌드할 경우 이름이 바뀐다고 하고
( Format of a C Decorated Name : https://msdn.microsoft.com/en-us/library/x7kb4e2f.aspx ),
Watcom C 컴파일러는 이 글 질문자께서 올리신 asm_io.asm 파일의 주석을 보면
이름 뒤에 언더스코어가 붙는다는 내용이 있네요.
gcc는 C 컴파일시 name mangling이 없는 것 같습니다만,
이 글을 보면 있는 경우도 있군요. 그래서, 실험해봤습니다.
linux에서 해보면 위와 같이 name mangling이 이뤄지지 않았습니다만,
windows에서 해보면 아래와 같이 나타났습니다. (32-bit, cygwin과 mingw 모두 나타남)
10년 쯤 전에 읽었던 자료와 오래된 경험에 간단한 검색을 버무려서 달았던 댓글인데,
아래의 언급은 좀 성급한 것 같고, name mangling이 일어나는 C 컴파일러들도 있다 정도가 좋겠네요.
음 ..
https://gustedt.wordpress.com/2011/06/24/name-mangling-in-c/
local static variable 에 대해서는 mangling 을 한다고 하네요.
되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』
컴파일러의 name mangling 과는 좀 다른
컴파일러의 name mangling 과는 좀 다른 성격이긴하지만, 라이브러리의 하위 호환성을 제공하기 위해 프로그래머가 의도적으로 조작하는 예도 있습니다.
~snip~
$ readelf -s /usr/lib64/libc.so.6 | grep sys_siglist
1659: 00000000003b4a80 512 OBJECT GLOBAL DEFAULT 28 sys_siglist@GLIBC_2.2.5
1661: 00000000003b4a80 520 OBJECT GLOBAL DEFAULT 28 sys_siglist@@GLIBC_2.3.3
2160: 00000000003b4a80 520 OBJECT GLOBAL DEFAULT 28 _sys_siglist@@GLIBC_2.3.3
2161: 00000000003b4a80 512 OBJECT GLOBAL DEFAULT 28 _sys_siglist@GLIBC_2.2.5
2940: 00000000003b4a80 520 OBJECT LOCAL DEFAULT 28 __new_sys_siglist
3308: 00000000003b4a80 520 OBJECT LOCAL DEFAULT 28 __GI__sys_siglist
3604: 00000000003b4a80 512 OBJECT LOCAL DEFAULT 28 _old2_sys_siglist
3824: 00000000003b4a80 520 OBJECT LOCAL DEFAULT 28 _new_sys_siglist
4144: 00000000003b4a80 512 OBJECT LOCAL DEFAULT 28 __old2_sys_siglist
4904: 00000000003b4a80 520 OBJECT GLOBAL DEFAULT 28 _sys_siglist@@GLIBC_2.3.3
5413: 00000000003b4a80 512 OBJECT GLOBAL DEFAULT 28 sys_siglist@GLIBC_2.2.5
5518: 00000000003b4a80 512 OBJECT GLOBAL DEFAULT 28 _sys_siglist@GLIBC_2.2.5
6670: 00000000003b4a80 520 OBJECT GLOBAL DEFAULT 28 sys_siglist@@GLIBC_2.3.3
~snip~
오래 전에... LD_PRELOAD 를 사용해서 alsa libasound 를 사용하는 유틸을 추적하려 시도했을 때 이것때문에 적잖이 당황했던 경험이 있습니다.
리눅스에서 gcc의 기본행동은 name
리눅스에서 gcc의 기본행동은 name mangling을 하지 않는 것으로 보이지만,
혹시 필요하다면 -fleading-underscore 옵션으로 언더스코어 프리픽스를 붙일 수도 있네요.
그리고 objcopy를 쓰면 컴파일된 결과물의 symbol 명을 자유롭게 편집할 수도 있군요.
평소에는 쓸 일이 없겠지만 가끔 특수한 상황을 극복할 때 도움이 될 지도 모르겠습니다.
http://stackoverflow.com/questions/6940384/how-to-deal-with-symbol-collisions-between-statically-linked-libraries/6940389#6940389
답변감사드립니다 모든글을 다읽어보았고 덕분에 새로운걸
답변감사드립니다 모든글을 다읽어보았고 덕분에 새로운걸 많이 얻어가네요~
제가 이해한 바로는 다음과같습니다
1.어셈블리에서는 호출규약을 직접 다 구현해야한다(파라미터전달,스택정리등등).
2.C언어에서는 함수나 전역변수의 스코프는 기본적으로 global이지만 어셈에서는 직접 global 디렉티브로 지정해줘야 외부모듈에서 extern 디렉티브로 인식할수가있다.
3.C컴파일러중에는 C심볼이름을 자동으로 바꾸는(네임맹글링)컴파일러도 있다.(수동으로 직접 네임맹글링을 수행할수도있다)
흠.. 근데 C언어에서는 네임맹글링을 왜 수행할까요? C++에서는 오버로딩,오버라이딩을 위해서라지만..
4.extern 지시어를 쓰면 외부 모듈의 심볼을 가져다가 사용할수있다.
해당 심볼은 어셈파일을 빌드할때는 오브젝트파일에 심볼정보만 등록되지만 링크 과정에서 실제 내용물이 실행파일로 합쳐진다.
5. 링커는 C표준라이브러리를 기본적으로 링크하도록 설정되어있다.
결론 : C와 어셈을 같이 사용해서(링크해서) 결과물을 만들경우 해당 C소스파일을 컴파일하는 컴파일러의 네임맹글링 정책을 잘 파악하고
어셈코드상의 심볼이름을 정해야한다.
*+ 오브젝트파일의 심볼들을 볼수있는 유틸은 'nm ' 이다 (처음알았습니다)
댓글 달기