2012-11-09 2 views
2

C 바인딩과 임의의 서명을 가진 함수가 주어진다면, 함수에 대한 포인터를 생성하고, 전달하고, 감싸고, 호출 할 수 있습니다.서명을 모르는 C 함수 호출을 래핑하는 방법은 무엇입니까?

int fun(int x, int y) 
{ 
    return x + y; 
} 
void* funptr() 
{ 
    return (void*)&fun; 
} 
int wrapfun(int x, int y) 
{ 
    // inject additional wrapper logic 
    return ((int (*)(int, int))funptr())(x, y); 
} 

호출자와 수신자가 동일한 호출 규칙을 따르고 서명에 동의하는 한 모든 것이 작동합니다.

이제 수천 개의 기능을 가진 라이브러리를 포장하고 싶다고합시다. nm 또는 readelf을 사용하여 래핑 할 모든 함수의 이름을 가져올 수 있지만 서명에 신경 쓸 필요가 없으며 라이브러리의 헤더 파일을 포함해야 할 필요가 있습니다.

버전과 플랫폼간에 발생하는 외관상의 변화로 인해 헤더를 깨끗하게 포함하는 것이 도움이되지 않을 수도 있습니다. 예를 들어 :

당신이 떠나거나 걸릴 수 있습니다 내 배경의 이론적 근거를,이다
// from openssl/ssl.h v0.9.8 
SSL_CTX* SSL_CTX_new(SSL_METHOD* meth); 
// from openssl/ssl.h v1.0.0 
SSL_CTX* SSL_CTX_new(const SSL_METHOD* meth); 

.

wrapfun의 호출자가 fun의 서명을 알고

// pseudocode 
void wrapfun() 
{ 
    return ((void (*)())funptr())(); 
} 

는 쓸 수있는 방법이 있나요하지만 wrapfun 자체를하지 않습니다에 :에 관계없이, 내 질문은 이것이다? 만약 컴파일 된 C 함수로부터 생성 된 어셈블리 보면

답변

5

하면 (AT & T 구문)

의 80186 동등

pushq %rbp 
movq %rsp, %rbp 
; body 
leave 
ret 

http://en.wikipedia.org/wiki/X86_instruction_listings 나열 leave 명령 래핑 모든 기능 체를 볼

movq %rbp, %rsp 
popq %rpb 

그래서 leave는 처음 두 줄의 바로 역이다 : 말에 긴장을 풀고 다음, 호출자의 스택 프레임을 저장하고 우리 자신의 스택 프레임을 만들 수 있습니다.

닫기 ret은 여기에있는 call의 역이며 http://www.unixwiz.net/techtips/win32-callconv-asm.html은이 쌍으로 연결된 명령어 중에 발생하는 명령어 포인터 레지스터의 숨겨진 푸시 및 팝을 표시합니다.

보이드 함수 포인터를 호출하기 때문에 컴파일러에 의해 기능 wrapfun 생성이 어셈블리의 그 자체로 작동하지 않는 이유. 우리가해야 할 일은 자신의 스택 프레임이 방법으로받지 않고, 직접적 fun의 호출에 호출자가 그것을 위해 설정 한 스택 프레임을 손으로 할 수있는 방식으로 래퍼를 만드는 것입니다. 다시 말해서 C 호출 규칙을 준수하고 동시에 위반하는 것입니다.

는 C 프로토 타입을 고려 우리가 fun를 원하기 때문에,

.file "wrapfun.s" 
    .globl wrapfun 
    .type wrapfun, @function 
wrapfun: 
    call funptr 
    jmp  *%rax 
    .size wrapfun, .-wrapfun 

기본적으로, 우리는 일반적으로 스택 포인터 및 기본 포인터 조작을 건너 (& T x86_64에 AT) 어셈블리 구현과 짝을

int wrapfun(int x, int y); 

님의 스택이 내 스택과 똑같이 보입니다. funptr에 대한 호출은 자신의 스택 공간을 만들고 그의 결과를 레지스터 RAX에 저장합니다. 우리의 스택 공간이 없기 때문에 호출자의 IP이 스택 맨 위에 잘 앉았 기 때문에 래핑 된 함수로 무조건 점프를하면됩니다. ret이 모두 뒤로 이동합니다. 이런 방식으로 함수 포인터가 호출되면 호출자가 스택을 설정 한 것을 볼 수 있습니다. 우리는 등, funptr에 매개 변수를 전달, 지역 변수를 사용해야하는 경우

, 우리는 항상 우리의 스택을 설정할 수 있습니다, 다음 호출하기 전에 그것을 해체 :

wrapfun: 
    pushq %rbp 
    movl %rsp, %rbp ; set up my stack 
    call funptr 
    leave    ; tear down my stack 
    jmp  *%rax 

을 다른 방법으로, 우리는이 논리를 포함 할 수있다 인라인 어셈블리로, 컴파일러는 전후에 무엇을 할 것 인 우리의 지식을 활용 :

void wrapfun() 
{ 
    void* p = funptr(); 
    __asm__(
     "movq -8(%rbp), %rax\n\t" 
     "leave\n\t" 
     "popq %rbx\n\t" 
     "call *%rax\n\t" 
     "pushq %rbx\n\t" 
     "pushq %ebp\n\t" // repeat initial function setup 
     "movq %rsp, %rbp" // so it can be torn down correctly 
    ); 
} 

이 방법은 마법에 앞서 C 지역 변수를 쉽게 선언의 장점이있다. 선언 된 마지막 로컬 변수는 RBP-sizeof (var)에 있고, 스택을 해체하기 전에 RAX에 저장합니다. 또 다른 가능한 이점은 별도의 소스 파일을 요구하지 않고 인라인 32 비트 또는 64 비트 어셈블리에 C 전 처리기를 사용할 수있는 기회입니다.

편집 : 단점은 이제 레지스터는 호출자가 사용하지 RBX을 요구함으로써 응용 프로그램의 이식성을 제한으로 IP를 저장하는 요구 사항입니다.

요약하면 대답은 '예'입니다. 손을 조금 더 기꺼이 가져 가려한다면, 서명을 모른 채 함수를 감쌀 수 있습니다. 이식성에 대한 약속 없음. 또한

2

당신은 또한 libffi (당신의 GCC 컴파일러 내에있을 수있다 해외 함수 인터페이스 라이브러리를) 사용을 고려해야합니다 Ryan's answer합니다. 귀하의 목표에 부합하고, "이동 가능"한 세부 사항을 추상화합니다 (많은 아키텍처, 시스템 및 ABI가 지원하는 경우).

+0

나는 내 자신의 문제에 대한 해결책을 찾고있는 동안 그리고이 대답을 게시 한 후에 libffi에 대한 연구를 해왔다. 그것은 멋진 아이디어, 그리고 컴파일 된 언어와 컴파일되지 않은 다양한 언어의 비트를 함께 붙일 수있는 효과적인 솔루션 인 것 같습니다. 적어도 한 파티가이 인터페이스에 대해 구현하려고한다면 ... –

관련 문제