2013-04-18 2 views
5

C 호출 규칙에 대해 배우고 싶습니다. C 디스 어셈블 된 호출 이해

#include <stdio.h> 
#include <stdlib.h> 

struct tstStruct 
{ 
    void *sp; 
    int k; 
}; 

void my_func(struct tstStruct*); 

typedef struct tstStruct strc; 

int main() 
{ 
    char a; 
    a = 'b'; 
    strc* t1 = (strc*) malloc(sizeof(strc)); 
    t1 -> sp = &a; 
    t1 -> k = 40; 
    my_func(t1); 
    return 0; 
} 

void my_func(strc* s1) 
{ 
     void* n = s1 -> sp + 121; 
     int d = s1 -> k + 323; 
} 

가 그럼 난 다음 명령을 사용하여 GCC를 사용 : 이렇게하려면 다음 코드 작성

gcc -S test3.c 

을하고 어셈블리를 함께했다. 내가 가진 전체 코드를 표시하지는 않지만 함수 my_func에 대한 코드를 붙여 넣습니다. 그것은 이것이다 :

my_func: 
.LFB1: 
.cfi_startproc 
pushq %rbp 
.cfi_def_cfa_offset 16 
.cfi_offset 6, -16 
movq %rsp, %rbp 
.cfi_def_cfa_register 6 
movq %rdi, -24(%rbp) 
movq -24(%rbp), %rax 
movq (%rax), %rax 
addq $121, %rax 
movq %rax, -16(%rbp) 
movq -24(%rbp), %rax 
movl 8(%rax), %eax 
addl $323, %eax 
movl %eax, -4(%rbp) 
popq %rbp 
.cfi_def_cfa 7, 8 
ret 
.cfi_endproc 

지금까지 내가 이해로 절차가 진행됩니다 먼저 발신자의 기본 포인터가 스택에 푸시하고 스택 포인터가 새의 스택을 설정하는 새 기본 포인터를 만들어 기능. 하지만 나머지는 이해가 안됩니다. 내가 아는 한, 인수 (또는 인수에 대한 포인터)는 스택에 저장됩니다. 그래서 제 2 명령의 목적, 여기

movq  -24(%rbp), %rax 

무엇이면 %의 RAX 레지스터의 내용이 24 바이트 떨어져 레지스터 %의 RBP의 어드레스로부터의 어드레스로 이동된다. 하지만 % rax에는 무엇이 있습니까 ???? ???? 아무것도 처음에 거기에 저장되지 ??? 나는 혼란 스럽다고 생각합니다. 이 기능의 작동 방식을 이해하는 데 도움을주십시오. 미리 감사드립니다.

+2

컴파일 심지어'GCC -fverbose-ASM -O -S'; 참고 문헌 [* lot *]을주는 [이 답장] (http://stackoverflow.com/a/16088155/841108)도 참조하십시오. –

+0

모든 참고 자료와 편집 팁을 보내 주셔서 감사합니다. – user2290802

답변

9

AT 구문이 Intel 구문과 AT & T 구문을 혼동하는 것입니다. 인텔에서

 
movq -24(%rbp), %rax 

 
mov rax,[rbp-24] 

은 그래서 raxrbp에 의해 해결 데이터를 이동 될 구문, 그리고 그 반대의 경우도 마찬가지입니다. 피연산자의 순서는 AT & T 구문의 src, dest이며 인텔 구문에서는 dest, src입니다.

그런 다음 분해하기 쉬운 GAS 지시어를 없애기 위해 gcc를 사용하여 코드를 gcc test3.c으로 간단히 어셈블하고 ndisasm -b 64 a.out으로 디스 어셈블합니다. NDISASM에 의해 생산 my_func 함수의 분해는 아래의 인텔 문법에 참고 : 리눅스에 대한 자세한 내용은

 
000005EF 55    push rbp 
000005F0 4889E5   mov rbp,rsp  ; create the stack frame. 
000005F3 48897DE8   mov [rbp-0x18],rdi ; s1 into a local variable. 
000005F7 488B45E8   mov rax,[rbp-0x18] ; rax = s1 (it's a pointer) 
000005FB 488B00   mov rax,[rax]  ; dereference rax, store into rax. 
000005FE 4883C079   add rax,byte +0x79 ; rax = rax + 121 
00000602 488945F8   mov [rbp-0x8],rax ; void* n = s1 -> sp + 121 
00000606 488B45E8   mov rax,[rbp-0x18] ; rax = pointer to s1 
0000060A 8B4008   mov eax,[rax+0x8] ; dereference rax+8, store into eax. 
0000060D 0543010000  add eax,0x143  ; eax = eax + 323 
00000612 8945F4   mov [rbp-0xc],eax ; int d = s1 -> k + 323 
00000615 5D    pop rbp 
00000616 C3    ret 

이 호출 규칙 (시스템 V ABI)를 - 64, What are the calling conventions for UNIX & Linux system calls on x86-64에 대한 답변을 참조하십시오.

+0

그러면 movq % rdi, -24 (% rbp) 명령의 의미는 무엇입니까? % rdi 레지스터에 무엇이 있습니까? – user2290802

+0

@ user2290802'% rdi'는 첫 번째 인수이며,이 경우에는'strc * s1'입니다. 디스 어셈블리에 대한 설명은 편집 된 답변을 참조하십시오. – nrz

+2

Intel 포맷으로 asm을 만들고 싶다면,'gcc -masm = intel -S'을 사용할 수 있습니다. 이것은 충분해야합니다. – perror

6

함수 (I는 불필요한 라인을 무시)과 같이 분해된다 : 여기서

pushq %rbp 
movq %rsp, %rbp 

이전 %rbp이 푸시된다

먼저 이전 스택 프레임을 저장할 수있다 스택은 함수가 끝날 때까지 저장됩니다. 그런 다음 %rbp은 새 %rsp의 값으로 설정됩니다 (push으로 저장된 %rbp보다 한 줄 아래에 있음).

movq %rdi, -24(%rbp) 

여기에 먼저 i386 system V ABIamd64 system V ABI 사이의 가장 큰 차이점 중 하나 알아야합니다.

i386 System V ABI에서 함수 인수는 스택을 통해 (그리고 스택을 통해서만) 전달됩니다. (이 경우 플로트 %xmm7%xmm0 그것을 정수이면 %rdi, %rsi, %rdx, %rcx, %r8%r9 등) AMD64 시스템 V ABI에 반대로, 상기 인수가 첫번째 레지스터를 통해 전달된다. 레지스터 수가 고갈되면 i386에서와 같이 나머지 인수가 스택에 푸시됩니다.

그래서 여기서 기계는 스택에 함수 (정수)의 첫 번째 인수를로드하고 있습니다.

movq -24(%rbp), %rax 

다른 하나에 하나 개의 레지스터에서 직접 데이터를 전송할 수 없기 때문에

%rdi의 내용은 다음 %rax에로드됩니다. 따라서 %rax에는이 함수의 첫 번째 (및 유일한) 인수가 저장됩니다.

movq (%rax), %rax 

이 명령은 포인터를 간접 참조하고 %rax 다시 결과를 저장하는 단계를 포함한다.

addq $121, %rax 

뾰족한 값에 121을 추가합니다.

movq %rax, -16(%rbp) 

얻어진 값을 스택에 저장한다.

movq -24(%rbp), %rax 

우리는 %rax에서 함수의 다시 첫 번째 인수 (우리가 -24(%rbp)에서 첫 번째 인수를 저장 기억),로드합니다. 이전으로

movl 8(%rax), %eax 
addl $323, %eax 

, 우리 포인터 역 참조 및 %eax에서 얻어진 값을 저장 한 후 우리가 323을 추가하고 %eax에 다시 넣어. 우리는 처리 된 값은 더 이상 이전에 같은 void* (이 64bit)하지만 int (32 비트)이 아니기 때문에, 우리는 %eax%rax로 전환 여기

참고.

movl %eax, -4(%rbp) 

마지막으로, 우리는 스택이 계산 결과 저장 (여기에 쓸모없는 것 같다,하지만 아마 컴파일러는 컴파일시에 감지하지 않았다 불필요한 일이다).

popq %rbp 
ret 

두 최종 지침은 다시 main 함수에 손을 부여하기 전에 이전 스택 프레임을 복원하고 있습니다.

이제이 동작이 더 명확 해지기를 바랍니다. 다음과 같은 명령을 입력하여 인텔 구문을 변경할 수 있습니다

+0

고마워요. 그래서 나는 혼란의 근원을 발견했다. 32 비트와 64 비트 문제입니다. – user2290802

+0

@perror 작은 설명 : "이전 % rbp가 함수의 끝까지 스택에 푸시됩니다. 그러면 % rsp가 새 % rbp의 값으로 설정됩니다 (이 값은 푸시가 발생하여 % rbp을 (를 저장했습니다.) " 나는 rsp와 rbp를 섞은 것 같아. "movq % rsp, % rbp" RBP를 RSP 값으로 설정하고 그 반대는 아닙니다 ... (AT & T 구문) – libjup

+0

당신이 맞습니다. 나는 다음과 같이 말했습니다 : "그러면 '% rbp'는 새로운'% rsp' ... "(나는 rsp와 rbp를 교환했습니다). 나는이 내용을 본문에서 고쳤다. 그것을 알아 줘서 고마워. – perror

1

:`GCC -fverbose-ASM의 -S`와 함께

$ gcc -S -masm=intel test3.c -o test3.s 
+0

안녕하세요. StackOverflow에 오신 것을 환영합니다. 질문에 잘 대답하는 방법을 익히십시오. :) 대답은 대답이 아니라 오히려 힌트이므로 주석에 넣어야합니다. 건배 :) – DawidPi

관련 문제