2012-10-07 3 views
2

IT 엔지니어링 학생이 여기 있습니다. 우리는 컨텍스트 스위칭을 가지고 놀아 달라는 요청을 받았고, 우리는 다소 조잡한 try/throw 시스템을 구현해야했습니다. 여기에 우리가 작성했던 코드입니다 :printf를 호출하면 분할 오류가 발생하지 않습니다.

struct ctx_s { 
    int esp; 
    int ebp; 
}; 

struct ctx_s * pctx; 

typedef int (func_t)(int); /* a function that returns an int from an int */ 

int try(func_t *f, int arg) 
{ 
    /* saving context by storing values of %esp and %ebp */  
    asm ("movl %%esp, %0" 
    : "=r"((*pctx).esp) 
    : 
    ); 

    asm ("movl %%ebp, %0" 
    : "=r"((*pctx).ebp) 
    : 
    ); 

    /* calling the function sent to try(), returning whatever it returns */ 
    return f(arg); 
} 

int throw(int r) 
{ 
    printf("MAGIC PRINT\n"); 

    static int my_return = 0; 
    /*^to avoid "an element from initialisation is not a constant" */ 
    my_return = r; 
    /* restituting context saved in try() */ 
    asm ("movl %0, %%esp" 
    : 
    : "r"((*pctx).esp) 
    ); 

    asm ("movl %0, %%ebp" 
    : 
    : "r"((*pctx).ebp) 
    ); 

    /* this return will go back to main() since we've restored try()'s context 
    so the return address is whatever called try... */ 
    /* my_return is static (=> stored in the heap) so it's not been corrupted, 
    unlike r which is now the second parameter received from try()'s context, 
    and who knows what that might be */ 
    return my_return; 
} 

pctx 두 INT 년대를 들고 단순한 구조로 글로벌 포인터는, f를 던져() (42)에 일부 반환 코드 #define을을 보내기를 호출하는 함수이며, main()은 본질적으로 pctx를 할당하고, result = try (f, 0)를 수행하고 결과를 출력합니다. 결과가 42 일 것으로 예상됩니다.

이제 MAGIC PRINT가 throw()에서 발견되었을 수 있습니다. 여기에 이유가 완전히 명확하지 않은 이유가 있습니다. 기본적으로 대부분의 학생들이 throw() 내부에서 segfaulting했다. 이 함수 내에서 printf()를 호출하면 프로그램이 겉으로보기에는 제대로 작동하고 교사는 시스템 호출이 효과가 있었을 것이라고 생각합니다.

실제로 설명을 얻지 못했기 때문에 gcc -S로 생성 된 어셈블리 코드를 두 버전 모두에서 비교해 보았습니다.하지만 printf()를 사용하거나 사용하지 않은 경우에는 많이 사용하지 못했습니다. printf와 함께

Breakpoint 1, throw (r=42) at main4.c:38 
(gdb) disass 
Dump of assembler code for function throw: 
0x0804845a <throw+0>: push %ebp 
0x0804845b <throw+1>: mov %esp,%ebp 
0x0804845d <throw+3>: mov 0x8(%ebp),%eax 
0x08048460 <throw+6>: mov %eax,0x8049720 
0x08048465 <throw+11>: mov 0x8049724,%eax 
0x0804846a <throw+16>: mov (%eax),%eax 
0x0804846c <throw+18>: mov %eax,%esp 
0x0804846e <throw+20>: mov 0x8049724,%eax 
0x08048473 <throw+25>: mov 0x4(%eax),%eax 
0x08048476 <throw+28>: mov %eax,%ebp 
0x08048478 <throw+30>: mov 0x8049720,%eax 
0x0804847d <throw+35>: pop %ebp 
0x0804847e <throw+36>: ret  
End of assembler dump. 
(gdb) c 
Continuing. 

Program received signal SIGSEGV, Segmentation fault. 
0xb7e846c0 in ??() 

() :

Breakpoint 1, throw (r=42) at main4.c:34 
(gdb) disassemble 
Dump of assembler code for function throw: 
0x0804845a <throw+0>: push %ebp 
0x0804845b <throw+1>: mov %esp,%ebp 
0x0804845d <throw+3>: sub $0x18,%esp 
0x08048460 <throw+6>: movl $0x80485f0,(%esp) 
0x08048467 <throw+13>: call 0x8048364 <[email protected]> 
0x0804846c <throw+18>: mov 0x8(%ebp),%eax 
0x0804846f <throw+21>: mov %eax,0x804973c 
0x08048474 <throw+26>: mov 0x8049740,%eax 
0x08048479 <throw+31>: mov (%eax),%eax 
0x0804847b <throw+33>: mov %eax,%esp 
0x0804847d <throw+35>: mov 0x8049740,%eax 
0x08048482 <throw+40>: mov 0x4(%eax),%eax 
0x08048485 <throw+43>: mov %eax,%ebp 
0x08048487 <throw+45>: mov 0x804973c,%eax 
0x0804848c <throw+50>: leave 
0x0804848d <throw+51>: ret  
End of assembler dump. 
(gdb) c 
Continuing. 
MAGIC PRINT 
result = 42 

Program exited normally. 
의 printf()없이

: 던져에 중단 점()의 여는 중괄호 (라인 33) 설정 및 GDB로 분해하는 것은 저에게이 준

나는 그것을 어떻게 만들어야할지 정말로 모른다. 분명히 일은 다르게 일어나고 있지만, 나는 두 경우 모두 무슨 일이 일어나고 있는지 이해하기가 어렵다. 그러니까 ... 내 질문은 본질적으로 : printf를 호출하는 것이 segfault를 던지지 않게하는 방법은 무엇입니까?

답변

1

좋아,이는 시도가 포함 된 방법은 %ebp%esp를 저장 지역 변수를위한 공간을 만들기 위해 %esp를 감소, 약간 나는 try 부분을 볼 수 없기 때문에 분석의 느슨한하지만, 표준 호출 규칙에서 판단이다 %esp%ebp을 저장하는 "try"코드를 실행하십시오.

일반적으로 함수가 종료되면 반환 전에 leave을 사용하여 변경 내용을 되돌립니다. 다시 %ebp%esp으로 복원하고 %ebp을 붙잡고 반환합니다. 이렇게하면 로컬 변수에 대한 공간이 예약되기 전에 %esp이 해당 지점으로 복원됩니다.

printf없는 버전의 문제는 첫째 %esp에 내용을 복원하지 않고 팝업 leave%ebp를 사용하지 않는다는 것입니다. ret 명령어는 로컬 변수를 팝업하고 그 변수로 돌아갑니다.최고의 결과는 아닙니다.

제 생각에는 함수에 로컬 변수가 없기 때문에 컴파일러는 %esp%ebp에서 복원 할 이유가 없다고 생각합니다. printf은 스택에 공간을 예약하므로 컴파일러는 해당 버전에서 %esp을 복원하기 전에 복원해야한다는 것을 알고 있습니다.

이론을 테스트하려면 어셈블러로 컴파일하고 바꾸십시오.

0x0804847d <throw+35>: pop %ebp 

떠나기 명령을 사용하고 결과를 어셈블합니다. 그것은 잘 작동해야합니다.

또는, %esp이 폐회되어 asm 명령에 gcc에 지시하여 대신 휴가를 생성 할 수 있다고 생각됩니다.

편집 : 사방으로 분명히 %esp 마킹은 GCC에 본질적으로 NOOP입니다 : -/

+0

감사합니다! 이것은 의미가 있으며, 이제 나는 그것에 대해 생각해 보았습니다. 저는 실제로이 "지역 변수 없음"측면을 언급하는 선생님을 기억합니다. 로컬 변수와 함수 매개 변수 사이에서 혼란스러워하기 때문에 나는 그것을 기각 한 것 같습니다 - throw는 매개 변수를 받기 때문에 스택에 예약 된 공간이없는 이유는 없습니다. 물론 매개 변수가 먼저 푸시되고, ** 다음에 ** 반환 주소가 반환되고, 그런 다음 로컬 변수 ... – Peniblec

1

ESP을 다른 기능에 저장된 값으로 "복원 중"입니다. 아마도 여기서 유용한 가치는 아닙니다.

"마법"코드와의 차이점은 컴파일러가 throw 함수에서 스택 프레임을 저장하고 복원한다는 점입니다.

끝에 leave 명령은 그냥 기능 항목에서 무엇에 다시 스택 포인터를 얻을 수 있습니다

mov %ebp, %esp 
pop %ebp 

에 해당합니다.

관련 문제