2017-04-06 1 views
0

상황은 다음과 같습니다 :glibc의 getpid 작업 절차는 무엇입니까?

나는 github에서 커널을 해킹하는 project을하려고합니다. 커널 버전은 linux-3.18.6입니다.

QEMU는 환경을 시뮬레이트하는 데 사용됩니다.

내 응용 프로그램에서 나는 그들을 따라가 syscall 프로 시저를 이해하려고 시도합니다. 내 목표를 완료하는 방법은 쉘 프로그램과 같습니다. 상대 시스템 호출을 실행하기위한 명령을 작성합니다. 어쩌면 사진을 통해 간단 할 수도 있습니다.

1 사용 API의 GETPID을 다음과 같이 some commands

코드는 간단하다.

int Getpid(int argc, char **argv) 
{ 
    pid_t pid; 
    pid = getpid(); 
    printf("current process's pid:%d\n",pid); 
    return 0; 
} 

2 int $ 0x80을 직접 사용하십시오.

내 응용 프로그램이 단지 pid 1로 실행되기 때문에 getpid 명령을 입력 할 때마다 1을 반환합니다. 물론 사실입니다.

이상한 일은 gysc를 사용하여 syscall 프로세스를 디버깅 할 때 getpid를 입력 할 때 한 번만 berakpoint sys_getpid에서 중지한다는 것입니다. 나는 그것을 반복해서 할 때 멈추지 않고 그냥 출력합니다.

int $ 0x80의 사용은 분명히 정확합니다.

문제를 해결하기 위해 몇 가지 조사를 수행했습니다. glibc 소스 (glibc-2.25) 코드를 다운로드하여 api getpid가 int $ 0x80을 래핑하는 방법을 확인하십시오. 불행히도, 거기에 없었거나 나는 단지 올바른 위치를 찾지 못했습니다.

일부 코드는 glibc에 있습니다.

pid_t getpid(void) 
{ 
    pid_t (*f)(void); 
    f = (pid_t (*)(void)) dlsym (RTLD_NEXT, "getpid"); 
    if (f == NULL) 
    error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, \"getpid\"): %s", dlerror()); 
    return (pid2 = f()) + 26; 
} 

잘못된 코드가있는 경우 알려주십시오.

코드에서 알 수 있듯이 getpid의 정의는 glibc에 포함되어 있지 않습니다. 일부 데이터를 읽은 후 누군가가 the VDSO...라고 말했습니다.

간단한 시스템 콜 비용 의 상당 부분을 차지하는 AFAIK는 사용자 공간에서 커널로 돌아가는 중입니다. 따라서 일부 시스템 호출의 경우 (아마도 gettimeofday, getpid ...) VDSO는 심지어 을 피할 수 있습니다 (기술적으로는 실제 시스템 콜을하지 않을 수도 있음). 사람 GETPID pgae에서

: glibc는 버전 2.3.4, GETPID에 대한 glibc는 래퍼 기능() 캐시의 PID, 이후

C 라이브러리/커널 차이 추가 시스템 호출을 피하기 위해 프로세스가 getpid()를 반복적으로 호출 할 때.일반적으로이 캐싱은 표시되지 않지만 올바른 작업은 fork (2), vfork (2) 및 clone (2)에 대한 래퍼 함수의 지원에 의존합니다. 응용 프로그램이 glibc 래퍼를 무시하고 syscall (2)를 호출하면 자식에서 getpid()를 호출하면 잘못된 값이 반환됩니다. 정확히 말하면 은 부모 프로세스의 PID를 반환합니다. disgl35c wrapper 함수를 통해 을 호출 할 때 getpid()가 잘못된 값을 반환 할 수있는 경우를 clone (2)를 참조하십시오.

많은 설명이 끝나기는하지만 API getpid의 작업 절차를 이해할 수 없습니다.

대조적으로 API 시간은 이해하기 쉽습니다. 시간의 정의 : 다음

time_t 
time (time_t *t) 
{ 
    INTERNAL_SYSCALL_DECL (err); 
    time_t res = INTERNAL_SYSCALL (time, err, 1, NULL); 
    /* There cannot be any error. */ 
    if (t != NULL) 
    *t = res; 
    return res; 
} 

,

#define INTERNAL_SYSCALL(name, err, nr, args...)   \ 
    internal_syscall##nr ("li\t%0, %2\t\t\t# " #name "\n\t", \ 
        "IK" (SYS_ify (name)),   \ 
        0, err, args) 

마지막으로, 임베디드 것 ASM, 커널 소스를 사용하는 일반적인 방법.

#define internal_syscall1(v0_init, input, number, err, arg1)  \ 
({         \ 
    long _sys_result;      \ 
            \ 
    {        \ 
    register long __s0 asm ("$16") __attribute__ ((unused))  \ 
     = (number);       \ 
    register long __v0 asm ("$2");     \ 
    register long __a0 asm ("$4") = (long) (arg1);   \ 
    register long __a3 asm ("$7");     \ 
    __asm__ volatile (      \ 
    ".set\tnoreorder\n\t"      \ 
    v0_init        \ 
    "syscall\n\t"       \ 
    ".set reorder"       \ 
    : "=r" (__v0), "=r" (__a3)     \ 
    : input, "r" (__a0)      \ 
    : __SYSCALL_CLOBBERS);      \ 
    err = __a3;       \ 
    _sys_result = __v0;      \ 
    }        \ 
    _sys_result;       \ 
}) 

누군가가 API getpid가 어떻게 작동하는지 명확하게 설명 할 수 있습니까? 왜 getpid가 syscall sys_getpid에 단 한번 트랩합니까? 가능한 경우 일부 참조를 존경합니다.

도움 주셔서 감사합니다.

+1

정확히 무엇이 당신의 질문입니까? 매뉴얼을 읽었습니다. glibc는 getpid-syscall에 의해 반환 된 값을 캐시합니다. 분명히이 캐시는 하위 프로세스에서 fork (2) 후에 다시 설정되어야합니다. –

+0

답변 해 주셔서 감사합니다. dlsym을 사용하는 getpid의 메커니즘은 무엇입니까? 왜 getpid의 실현은 다른 것들과 다른가요? 그게 내가 알고 싶은 것. 감사. –

답변

1

우선 glibc 소스 코드가 탐색하기가 거의 불가능하다는 점에 유의하십시오.

getpid()는 사용자가 알아챈 것처럼 그 결과를 캐시합니다.

pid_t getpid(void) 
{ 
    pid_t (*f)(void); 
    f = (pid_t (*)(void)) dlsym (RTLD_NEXT, "getpid"); 
    if (f == NULL) 
    error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, \"getpid\"): %s", dlerror()); 
    return (pid2 = f()) + 26; 
} 

단지 래퍼입니다

처럼 보이는 당신이 발견 코드입니다. 그것은 getpid 기호를 찾고 해당 기능을 호출합니다. 그 기능을 찾아야합니다. sysdeps/unix/sysv/linux/getpid.c 파일에있는 __getpid() 함수의 별칭이며이 게시물 하단에도 표시됩니다.

이제 glibc 소스 코드가 현재 glibc와 일치하지 않을 수 있습니다. 2017 년 11 월 캐싱 인 getpid()가 this commit에 캐싱하는 것과 관련하여 큰 변화가있었습니다. 변경 사항은 다음과 같습니다. http://repo.or.cz/glibc.git/blob/93eb85ceb25ee7aff432ddea0abf559f53d7a5fc:/sysdeps/unix/sysv/linux/getpid.c

static inline __attribute__((always_inline)) pid_t 
really_getpid (pid_t oldval) 
{ 
    if (__glibc_likely (oldval == 0)) 
    { 
     pid_t selftid = THREAD_GETMEM (THREAD_SELF, tid); 
     if (__glibc_likely (selftid != 0)) 
    return selftid; 
    } 

    INTERNAL_SYSCALL_DECL (err); 
    pid_t result = INTERNAL_SYSCALL (getpid, err, 0); 

    /* We do not set the PID field in the TID here since we might be 
    called from a signal handler while the thread executes fork. */ 
    if (oldval == 0) 
    THREAD_SETMEM (THREAD_SELF, tid, result); 
    return result; 
} 
#endif 

pid_t 
__getpid (void) 
{ 
#if !IS_IN (libc) 
    INTERNAL_SYSCALL_DECL (err); 
    pid_t result = INTERNAL_SYSCALL (getpid, err, 0); 
#else 
    pid_t result = THREAD_GETMEM (THREAD_SELF, pid); 
    if (__glibc_unlikely (result <= 0)) 
    result = really_getpid (result); 
#endif 
    return result; 
} 

libc_hidden_def (__getpid) 
weak_alias (__getpid, getpid) 
libc_hidden_def (getpid) 
다음과 같습니다의 glibc-2.25 2017년 2월

번 이상 콜)를 GETPID을 (호출 피하기 위해 값을 캐시 오래된 GETPID() 구현에 발표의 일부, 여기에서 볼 수있다

+0

자세한 답변을 보내 주셔서 감사합니다. 어쩌면 당신의 의미를 이해합니다. 당신의 메시지를 얻은 후에, 나는 우분투 glibc 버전을 ldd로 체크했는데, 커널을 컴파일했다. 당신이 지적한대로, glibc-2.23이고 glibc-2.23에서 지적한 코드를 발견했습니다. 따라서 피는 해결되어야합니다.그러나, 나는 아직도 getpid의 실현이 커널 모드에 들어가야하는 다른 api 또는 시간과 다른 이유를 알 수 없다. 또한 getpid에서 사용하는 dlsym의 메커니즘을 이해하지 못합니다. 가능하면 세부 사항에 대한 참조를 제공 할 수 있습니까? 나는 더 깊게 파고 싶다. –

+0

@ a-thorn 프로세스의 PID는 결코 변경되지 않습니다. 따라서 glibc는 커널에서 PID를 한 번 가져온 다음 나중에 다른 시스템 호출을 수행하지 않고도 캐시 할 수 있습니다. 시간() 호출이 같은 방식으로 캐시 될 수없는 이유는 명백합니다. 다른 대부분의 시스템 호출에서도 마찬가지입니다. 결과를 캐시하는 것이 타당하지 않습니다. dlsym() 함수를 사용하여 getpid를 조회 한 코드는'./elf/restest2.c'에서 나온 것처럼 보이지만이 파일은 getpid() 구현이 아닌 glibc의 testsuite의 일부입니다. – nos

+0

답변 해 주셔서 감사합니다. 네, 이제 마침내 그 차이를 알게되었습니다. 제 멍청한 것을 용서해주세요. 이제는 getpid에 대해 testsuite라는 것을 알았지 만, 실제로 getpid() 구현을 찾을 수 없습니다. 나 한테 그걸 지적 할 수 있니? 나는 getpid를 사용하지만, 나는 __getpid 구현을 찾는다. 또한 time()은 int $ 0x80을 사용하여 커널 모드로 들어가기 때문에 커널은 구현 sys_time을 찾는다. getpid()에 관해서는, 나는 혼란 스럽다. 주의를 기울이자. –

관련 문제