请让我知道什么是_nocancel()系统调用(例如__pwrite_nocancel(),以及是否有办法创建一个LD_PRELOAD库来拦截这些调用。这里有一些背景知识:
我正在研究Oracle数据库的功能,并希望使用LD_PRELOAD添加一个小的填充层,以捕获有关用户空间中呼叫的信息。我知道使用系统分接头捕获此信息的其他方法,但使用LD_PRELOAD是客户的一项硬性要求。
strace表明这个特殊的进程反复调用pwrite();类似地,pstack堆栈跟踪显示__pwrite_nocancel()被调用为堆栈中的最后一个条目。我尝试复制我自己的__libc_pwrite()函数并声明
extern ssize_t pwrite(int fd, const void *buf, size_t numBytes, off_t offset)__attribute__((weak, alias ( "__libc_pwrite")));
但是当我链接库并运行nm -a | grep pwrite时,我得到了这个:
000000000006c190 T __libc_pwrite
000000000006c190 W pwrite
相比之下,nm -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 <drepper@cygnus.com>, 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 :(得分:3)
pwrite_nocancel()
等等不是Linux中的系统调用。它们是C库内部的函数,与pthread和线程消除紧密结合。
_nocancel()
版本的行为与原始函数完全相同,只是这些版本不是线程取消点。
大多数I / O功能都是取消点。也就是说,如果线程取消类型是延迟并且取消状态是启用,并且进程中的另一个线程请求取消线程,则线程将取消(退出) )进入取消点时。有关详细信息,请参阅man 3 pthread_cancel
,man 3 pthread_setcancelstate
和man 3 pthread_setcanceltype
。
不幸的是,pwrite_nocancel()
和其他_nocancel()
函数是pthreads库的内部(本地)函数,因此很难插入;它们不是动态符号,因此动态链接器不能覆盖它们。在这一点上,我怀疑,但不确定,介入它们的方法将涉及通过直接跳转到您自己的代码来重写库代码的开头。
如果它们是导出的(全局)函数,它们可以像任何其他库函数一样插入(这些函数由pthread库libpthread
提供),使用常规方法。 (在我自己的答案中,您可能会发现this,this和this信息丰富。否则,只需搜索 LD_PRELOAD 示例。)
答案 1 :(得分:0)
鉴于您想要通过LD_PRELOAD
进行干预,您需要查看rtld正在使用的相同符号。因此,请使用nm -a
而不是objdump -T
。如果您没有在那里列出您想要的符号,则无法插入它。所有导出的glibc符号都有一个&#34;版本&#34;由rtld进行比较的字符串,允许使用相同符号的多个版本(这就是为什么即使nm -D
没有用 - 它也没有显示版本)。要插入某些内容,您应该拥有相同的版本字符串。这可以使用版本脚本(请参阅info ld
),.symver
asm指令或调用该指令的宏来完成。请注意带有&#34; private&#34;的符号。版本可能会在libc的任何新版本中更改,并且其他符号可能会被任何新版本的新版本取代(符号仍然有效,但针对新版本编译的应用程序不再使用它们)。