2015-08-30 73 views
1

請讓我知道什麼是_nocancel()系統調用(如__pwrite_nocancel()的方式,以及是否有創建方式一個LD_PRELOAD庫來攔截這些調用,下面是一些背景:什麼是_nocancel()系統調用在Linux和有使用LD_PRELOAD去攔截

我正在調查Oracle數據庫的功能,並且希望使用LD_PRELOAD添加一個小的填充圖層來捕獲用戶空間中的調用信息。我知道使用系統抽頭捕獲這些信息的其他方法,但使用LD_PRELOAD是來自客戶的一個硬性要求。 strace顯示此特定進程正在重複調用pwrite();同樣,pstack堆棧跟蹤顯示__pwrite_nocancel( )被稱爲t他最後一次進入堆棧。我試圖複製我自己__libc_pwrite()函數,並宣佈 extern ssize_t pwrite(int fd, const void *buf, size_t numBytes, off_t offset)__attribute__((weak, alias ("__libc_pwrite"))); 但是當我鏈接庫和運行納米-a | grep的PWRITE,我得到這個:

000000000006c190 T __libc_pwrite 
000000000006c190 W pwrite 
相比之下

,納米-a/lib64的/ libpthread.so.0 | grep的PWRITE給出如下:

000000000000eaf0 t __libc_pwrite 
000000000000eaf0 t __libc_pwrite64 
000000000000eaf0 W pwrite 
000000000000eaf0 t __pwrite 
000000000000eaf0 W pwrite64 
000000000000eaf0 W __pwrite64 
0000000000000000 a pwrite64.c 
000000000000eaf9 t __pwrite_nocancel 

我已經注意到,_nocancel版本是提前__pwrite的只有9個字節,但看源代碼,我不能確定在何處正在創建:

/* Copyright (C) 1997, 1998, 2000, 2002, 2003, 2004, 2006 
    Free Software Foundation, Inc. 
    This file is part of the GNU C Library. 
    Contributed by Ulrich Drepper <[email protected]>, 1997. 
    The GNU C Library is free software; you can redistribute it and/or 
    modify it under the terms of the GNU Lesser General Public 
    License as published by the Free Software Foundation; either 
    version 2.1 of the License, or (at your option) any later version. 
    The GNU C Library is distributed in the hope that it will be useful, 
    but WITHOUT ANY WARRANTY; without even the implied warranty of 
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
    Lesser General Public License for more details. 
    You should have received a copy of the GNU Lesser General Public 
    License along with the GNU C Library; if not, see 
    <http://www.gnu.org/licenses/>. */ 

#include <assert.h> 
#include <errno.h> 
#include <unistd.h> 
#include <endian.h> 

#include <sysdep-cancel.h> 
#include <sys/syscall.h> 
#include <bp-checks.h> 

#include <kernel-features.h> 

#ifdef __NR_pwrite64   /* Newer kernels renamed but it's the same. */ 
# ifdef __NR_pwrite 
# error "__NR_pwrite and __NR_pwrite64 both defined???" 
# endif 
# define __NR_pwrite __NR_pwrite64 
#endif 

#if defined __NR_pwrite || __ASSUME_PWRITE_SYSCALL > 0 

# if __ASSUME_PWRITE_SYSCALL == 0 
static ssize_t __emulate_pwrite (int fd, const void *buf, size_t count, 
       off_t offset) internal_function; 
# endif 

ssize_t 
__libc_pwrite (fd, buf, count, offset) 
    int fd; 
    const void *buf; 
    size_t count; 
    off_t offset; 
{ 
    ssize_t result; 

    if (SINGLE_THREAD_P) 
    { 
     /* First try the syscall. */ 
     result = INLINE_SYSCALL (pwrite, 6, fd, CHECK_N (buf, count), count, 0, 
        __LONG_LONG_PAIR (offset >> 31, offset)); 
# if __ASSUME_PWRITE_SYSCALL == 0 
     if (result == -1 && errno == ENOSYS) 
     /* No system call available. Use the emulation. */ 
     result = __emulate_pwrite (fd, buf, count, offset); 
# endif 
     return result; 
    } 

    int oldtype = LIBC_CANCEL_ASYNC(); 

    /* First try the syscall. */ 
    result = INLINE_SYSCALL (pwrite, 6, fd, CHECK_N (buf, count), count, 0, 
       __LONG_LONG_PAIR (offset >> 31, offset)); 
# if __ASSUME_PWRITE_SYSCALL == 0 
    if (result == -1 && errno == ENOSYS) 
    /* No system call available. Use the emulation. */ 
    result = __emulate_pwrite (fd, buf, count, offset); 
# endif 

    LIBC_CANCEL_RESET (oldtype); 

    return result; 
} 

strong_alias (__libc_pwrite, __pwrite) 
weak_alias (__libc_pwrite, pwrite) 

# define __libc_pwrite(fd, buf, count, offset) \ 
    static internal_function __emulate_pwrite (fd, buf, count, offset) 
#endif 

#if __ASSUME_PWRITE_SYSCALL == 0 
# include <sysdeps/posix/pwrite.c> 
#endif 

任何幫助表示讚賞。

回答

0

既然你想通過LD_PRELOAD夾着,你需要看看rtld使用相同的符號。因此,而不是nm -a,請使用objdump -T。如果你想要的符號沒有在那裏列出,你不能介入它。所有導出的glibc符號都有一個「版本」字符串,該字符串通過rtld進行比較,並允許具有相同符號的多個版本(這就是爲什麼即使nm -D也沒用 - 它不顯示版本)。要介紹一些東西,你應該有相同的版本字符串。這可以使用版本腳本(請參閱info ld),.symver asm指令或調用該指令的宏來完成。請注意,帶有「私有」版本的符號可能會在libc的任何新版本中發生變化,其他符號可能會在任何新版本中被新版本取代(符號仍然有效,但針對新版本編譯的應用程序不會使用他們了)。

+0

感謝您的信息;幸運的是,儘管LD_PRELOAD似乎無視版本符號(可能因爲只定義了一個版本)而覆蓋了該函數,所以我不必經歷櫻桃挑選符號的麻煩。當我做objdump -T時,libpthread符號的確顯示爲GLIBC-2.2.5,而我的是Base。經過一番挖掘,__pwrite_nocancel()實際上來自我沒有導出的pwrite64符號。在添加了另一個弱別名之後,沒關係。再次感謝您的回覆。 – Leo

2

pwrite_nocancel()等等都沒有在Linux的系統調用。它們是C庫內部的函數,並且與pthread和線程取消緊密結合。

_nocancel()版本有着完全一樣的原始功能,但這些版本不是線程取消點。

大多數I/O功能都是取消點。也就是說,如果線程取消類型是推遲和取消狀態啓用,並在過程中另一個線程已經請求被取消的線程,該線程將取消(出口)進入取消點時。進一步的細節參見man 3 pthread_cancel,man 3 pthread_setcancelstateman 3 pthread_setcanceltype

不幸的是,pwrite_nocancel()_nocancel()功能是內部(本地)到pthreads庫,因此,是相當困難的干預;它們不是動態符號,所以動態鏈接器不能覆蓋它們。在這一點上,我懷疑,但不確定,干涉它們的方式將涉及重寫庫代碼的開始,直接跳轉到您自己的代碼。

如果它們是導出(全局)函數,則可以像使用常規方法一樣插入,就像任何其他庫函數(這些由pthread庫提供,libpthread)一樣。 (這裏我自己的答案,你可能會發現thisthisthis信息。否則,只搜索LD_PRELOAD例如。)

+0

謝謝您的詳細解答,特別是有用的鏈接。幸運的是,好的LD_PRELOAD最終還是有效的。在使用gdb並附加到oracle並使用'layout asm'模式後,我發現儘管它沒有出現在堆棧中,但__pwrite_nocancel實際上是一個從pwrite64符號開始的標籤。在添加行extern之後,我們可以使用pset64(int fd,const void * buf,size_t numBytes,off_t offset)__屬性__((弱,別名(「__libc_pwrite」)));到我的代碼,一切開始工作。 – Leo

+0

@Leo,所以'_nocancel()'變體只能從庫函數本身調用,因此不需要單獨插入;插入標準庫函數也會照顧它們。有道理,真的。我懶得去查看pthreads來源,看看是否從其他地方調用_nocancel()變體。它們對圖書館來說是本地的,意味着你不應該直接調用它們 - 除非你獲得了一個指向它們的函數指針。動態鏈接器肯定不會。 –