NASM 어셈블리(리눅스) 에서 C표준 라이브러리 함수 호출하기

노력하는자의 이미지

안녕하세요 어셈블리 공부하는 컴공입니다.
일단 제가 지금 질문하려는 내용은 제목그대로입니다
리눅스 환경에서 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: 
첨부파일 크기
Plain text icon asm_io.txt8.76 KB
twinwings의 이미지

어셈블리를 잘 모르지만 매크로 정의하는 이유는,

보시다시피 ELF_TYPE나 WATCOM 따라 호출해야 하는 심볼이 다릅니다.

하지만 매크로를 통해서 한 줄만 바꾸면 되도록 작성한 것 처럼 보입니다.

만약 매크로를 정의하지 않았다면 플랫폼이 바뀔 경우 소스 코드의 매 호출부분을

모두 바꿔줘야겠지요.

노력하는자의 이미지

답변감사드립니다 덕분에 첫번째는 궁금증이 해결됬습니다.
여러 컴파일 환경에서 서로다른 표준라이브러리 심볼 이름들을 어셈코드상에서 동일하게 처리하기 위함이군요

chanik의 이미지

[1] 첫 번째 의문

Quote:
여기서 왜 굳이 C표준 함수 심볼명인 scanf, printf 등등에 Underscore(밑줄) 을 붙여서 매크로로 재정의 한다음 사용하는지..
그리고 Watcom이라는건 무슨 환경인지.. 이게 첫번째 의문이구요

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이 일어나지 않았네요.

$ nm -Ag /lib64/libc.so.6 | grep " [fs]*printf$"
/lib64/libc.so.6:00000038e3a4cbe0 T fprintf
/lib64/libc.so.6:00000038e3a4cc70 T printf
/lib64/libc.so.6:00000038e3a4cdb0 T sprintf
 
$ nm -Ag /usr/lib64/libc.a 2>/dev/null | grep " [fs]*printf$" | grep " T "
/usr/lib64/libc.a:fprintf.o:0000000000000000 T fprintf
/usr/lib64/libc.a:printf.o:0000000000000000 T printf
/usr/lib64/libc.a:sprintf.o:0000000000000000 T sprintf

그래서, 리눅스용으로 nasm -f elf -d ELF_TYPE asm_io.asm 명령으로 어셈블하면
아래와 같이 언더스코어(_) 없는 버전으로 심볼을 재정의하고 있는 것입니다.
기본형을 언더스코어 버전으로 해 놓은 이유는 어셈블리 소스 작성자가
그것이 가장 전형적인 name mangling 형태라고 판단한 것 아닐까요?

;
; 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은 C/C++ 컴파일러 가운데 하나입니다.
( https://en.wikipedia.org/wiki/Watcom_C/C%2B%2B_compiler )
asm_io.asm 소스의 주석을 보면, Watcom C 컴파일러는
name mangling도 함수 이름 뒤에 언더스코어를 붙이는 식으로 좀 특이하게 하고
함수에 대한 calling convention도 다른 컴파일러와 좀 다르게 하는 모양이네요.

[2] 두 번째 의문

Quote:
위에서 재정의한 C표준함수들을 extern 명령어로 인식시키고있는데..
어셈블러는 이놈들을(extern 선언된놈들) 빌드할때 어디서 인식하는건가요?
C언어에서는 stdio.h라는 헤더를 인클루드해서 C표준함수를 사용하잖아요?
근대 이 어셈코드를보면 아무리봐도 "printf, scanf라는 바로 ~ 이다" 하는 내용이없습니다. 그냥 위처럼 extern 선언한것뿐입니다.

아래의 샘플에서 보실 수 있듯, 굳이 stdio.h 전체를 #include 하지 않더라도
printf 함수에 대한 extern 선언 한 줄만 있으면 printf 함수를 사용할 수 있습니다.

asm_io.asm 파일에서 printf 등을 쓰기 위해 하고 있는 일도
이와 같은 것이라고 보면 혼란이 사라지죠?

extern int printf (__const char *__restrict __format, ...);
 
int main()
{
    printf("extern declaration is enough\n");
    return 0;
}

$ gcc test.c
$ ./a.out
extern declaration is enough

chanik의 이미지

두 번째 의문을 다시 읽어보니, C언어에서 printf()를 쓸 때는

extern int printf (__const char *__restrict __format, ...);
식으로 함수 프로토타입을 선언해주고 있는데, 어셈블리 언어에서는 고작
extern  _printf
이 정도로 끝나는 점이 이상하다는 것이 요지같네요.

함수호출이 제대로 이뤄지려면 파라미터 갯수/타입, 반환값이 파악되어야 하는 것 말고도,
어떤 식으로 파라미터를 넘겨주고 (스택, 레지스터, 둘을 섞은 형태, 스택은 어떻게 쓸 지...),
어떤 식으로 스택의 파라미터를 청소할지 (호출자가 청소할 지, 호출된 함수 내에서 청소할 지),
어떤 식으로 반환값을 받아올 지 (어떤 레지스터를 이용할 지 ...) 등도 합의되어야 하는데
이런 것이 calling convention으로 정의됩니다.

C 언어에서는 이런 부분을 컴파일러가 책임지기 때문에, 대상 함수에 대한 프로토타입이 요구됩니다.
프로토타입에는 파라미터/반환값 정보 뿐 아니라 caling convention도 함축적으로 담겨 있습니다.
(프로토타입에는 cdecl, stdcall, ... 식으로 calling convention 지정이 가능한데,
detail은 컴파일러 또는 ABI에 의해 결정된다고 해야 할 것 같습니다)

그에 비해 어셈블러는 이런 것을 전혀 신경쓰지 않으므로 프로토타입 선언같은 것은 필요 없고
함수의 calling convention에 맞게 코딩하는 책임은 그냥 프로그래머에게 떠넘겨집니다.
심볼 이름을 name mangling 신경써서 정확히 지정했다면,
그 함수 호출 관행에 맞게 호출코드를 짜면 됩니다.

twinwings의 이미지

C++이 아닌 C에서도 네임맹글링을 수행하나요?

nm 이용해서 심볼 이름 확인했을때 한번도 이름이 바뀐걸 본 적이 없어서 질문드립니다.

chanik의 이미지

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이 없는 것 같습니다만,
이 글을 보면 있는 경우도 있군요. 그래서, 실험해봤습니다.

$ cat m.c
int __attribute__((cdecl))    f (int x) { return 0; }
int __attribute__((stdcall))  g (int y) { return 0; }
int __attribute__((fastcall)) h (int z) { return 0; }
 
$ lsb_release -d
Description:    CentOS release 5.11 (Final)
 
$ uname -a
Linux dev-xt 2.6.18-409.el5 #1 SMP Tue Mar 15 18:13:50 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux
 
$ gcc44 --version
gcc44 (GCC) 4.4.7 20120313 (Red Hat 4.4.7-1)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
$ gcc44 -c -o m.o m.c
m.c:1: warning: ‘cdecl’ attribute ignored
m.c:2: warning: ‘stdcall’ attribute ignored
m.c:3: warning: ‘fastcall’ attribute ignored
$ nm m.o
0000000000000000 T f
000000000000000e T g
000000000000001c T h
 
$ gcc44 -m32 -c -o m.o m.c
$ nm m.o
0000000000000000 T f
000000000000000e T g
000000000000001c T h

linux에서 해보면 위와 같이 name mangling이 이뤄지지 않았습니다만,
windows에서 해보면 아래와 같이 나타났습니다. (32-bit, cygwin과 mingw 모두 나타남)

$ gcc --version
gcc (GCC) 5.3.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
$ gcc -c -o m.o m.c
$ nm m.o
00000000 b .bss
00000000 d .data
00000000 r .eh_frame
00000000 r .rdata$zzz
00000000 t .text
00000016 T @h@4
00000000 T _f
0000000a T _g@4
 
$ /c/MinGW/bin/gcc --version
gcc.exe (tdm-1) 5.1.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 
$ /c/MinGW/bin/gcc -c -o m.o m.c
$ nm m.o
00000000 b .bss
00000000 d .data
00000000 r .rdata$zzz
00000000 t .text
00000016 T @h@4
00000000 T _f
0000000a T _g@4

10년 쯤 전에 읽었던 자료와 오래된 경험에 간단한 검색을 버무려서 달았던 댓글인데,
아래의 언급은 좀 성급한 것 같고, name mangling이 일어나는 C 컴파일러들도 있다 정도가 좋겠네요.

Quote:
변형 없이 그냥 컴파일해주는 경우도 있지만, 대개는 앞에 언더스코어(_)를 붙인다든지
뒤에 @를 붙인다든지 등등 이런저런 약간의 변형을 합니다.

ymir의 이미지

https://gustedt.wordpress.com/2011/06/24/name-mangling-in-c/

local static variable 에 대해서는 mangling 을 한다고 하네요.

$ cat m.c
#include <stdio.h>
 
int foo = 1;
 
int main(void)
{
        printf("foo = %d, at %p\n", foo, &foo);
 
        static volatile int foo = 2;
 
        printf("foo = %d, at %p\n", foo, &foo);
 
        return 0;
}
$ gcc -g -W -Wall m.c
$ ./a.out
foo = 1, at 0x601040
foo = 2, at 0x601044
$ nm a.out | grep foo
0000000000601040 D foo
0000000000601044 d foo.2180

되면 한다! / feel no sorrow, feel no pain, feel no hurt, there's nothing gained.. only love will then remain.. 『 Mizz 』

bushi의 이미지

컴파일러의 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 를 사용하는 유틸을 추적하려 시도했을 때 이것때문에 적잖이 당황했던 경험이 있습니다.

chanik의 이미지

리눅스에서 gcc의 기본행동은 name mangling을 하지 않는 것으로 보이지만,
혹시 필요하다면 -fleading-underscore 옵션으로 언더스코어 프리픽스를 붙일 수도 있네요.

$ cat m.c
int __attribute__((cdecl))    f (int x) { return 0; }
int __attribute__((stdcall))  g (int y) { return 0; }
int __attribute__((fastcall)) h (int z) { return 0; }
 
$ gcc44 -c -o m.o m.c
...
$ nm m.o
0000000000000000 T f
000000000000000e T g
000000000000001c T h
 
$ gcc44 -fleading-underscore -c -o m.o m.c
...
$ nm m.o
0000000000000000 T _f
000000000000000e T _g
000000000000001c T _h

그리고 objcopy를 쓰면 컴파일된 결과물의 symbol 명을 자유롭게 편집할 수도 있군요.
평소에는 쓸 일이 없겠지만 가끔 특수한 상황을 극복할 때 도움이 될 지도 모르겠습니다.

http://stackoverflow.com/questions/6940384/how-to-deal-with-symbol-collisions-between-statically-linked-libraries/6940389#6940389

$ nm m.o
0000000000000000 T f
000000000000000e T g
000000000000001c T h
 
$ objcopy --prefix-symbol=_ m.o
$ nm m.o
0000000000000000 T _f
000000000000000e T _g
000000000000001c T _h
 
$ objcopy --prefix-symbol=my m.o
$ nm m.o
0000000000000000 T my_f
000000000000000e T my_g
000000000000001c T my_h
 
$ objcopy --redefine-sym my_f=_f@4 m.o
$ nm m.o
0000000000000000 T _f@4
000000000000000e T my_g
000000000000001c T my_h

노력하는자의 이미지

답변감사드립니다 모든글을 다읽어보았고 덕분에 새로운걸 많이 얻어가네요~

제가 이해한 바로는 다음과같습니다

1.어셈블리에서는 호출규약을 직접 다 구현해야한다(파라미터전달,스택정리등등).

2.C언어에서는 함수나 전역변수의 스코프는 기본적으로 global이지만 어셈에서는 직접 global 디렉티브로 지정해줘야 외부모듈에서 extern 디렉티브로 인식할수가있다.

3.C컴파일러중에는 C심볼이름을 자동으로 바꾸는(네임맹글링)컴파일러도 있다.(수동으로 직접 네임맹글링을 수행할수도있다)
흠.. 근데 C언어에서는 네임맹글링을 왜 수행할까요? C++에서는 오버로딩,오버라이딩을 위해서라지만..

4.extern 지시어를 쓰면 외부 모듈의 심볼을 가져다가 사용할수있다.
해당 심볼은 어셈파일을 빌드할때는 오브젝트파일에 심볼정보만 등록되지만 링크 과정에서 실제 내용물이 실행파일로 합쳐진다.

5. 링커는 C표준라이브러리를 기본적으로 링크하도록 설정되어있다.

결론 : C와 어셈을 같이 사용해서(링크해서) 결과물을 만들경우 해당 C소스파일을 컴파일하는 컴파일러의 네임맹글링 정책을 잘 파악하고
어셈코드상의 심볼이름을 정해야한다.

*+ 오브젝트파일의 심볼들을 볼수있는 유틸은 'nm ' 이다 (처음알았습니다)

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.