gpt4 book ai didi

c - 包装 abort() 系统调用时的奇怪行为

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:19:37 25 4
gpt4 key购买 nike

为了编写单一测试,我需要包装 abort() 系统调用。

这是一段代码:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

extern void __real_abort(void);
extern void * __real_malloc(int c);
extern void __real_free(void *);


void __wrap_abort(void)
{
printf("=== Abort called !=== \n");
}

void * __wrap_malloc(int s)
{
void *p = __real_malloc(s);
printf("allocated %d bytes @%p\n",s, (void *)p);
return p;
}

void __wrap_free(void *p)
{
printf("freeing @%p\n",(void *)p);
return __real_free((void *)p);
}


int main(int ac, char **av)
{
char *p = NULL;
printf("pre malloc: p=%p\n",p);
p = malloc(40);
printf("post malloc p=%p\n",p);

printf("pre abort\n");
//abort();
printf("post abort\n");

printf("pre free\n");
free(p);
printf("post free\n");
return -1;
}

然后我使用以下命令行编译它:

gcc -Wl,--wrap=abort,--wrap=free,--wrap=malloc -ggdb -o test test.c

运行它给出以下输出:

$ ./test
pre malloc: p=(nil)
allocated 40 bytes @0xd06010
post malloc p=0xd06010
pre abort
post abort
pre free
freeing @0xd06010
post free

所以一切都很好。现在让我们测试相同的代码,但取消注释 abort() 调用:

$ ./test
pre malloc: p=(nil)
allocated 40 bytes @0x1bf2010
post malloc p=0x1bf2010
pre abort
=== Abort called !===
Segmentation fault (core dumped)

我真的不明白为什么我在模拟 abort() 系统调用时会出现段错误...欢迎提出任何建议!

我在 x86_64 内核上运行 Debian GNU/Linux 8.5。 Machine 是一款基于 Core i7 的笔记本电脑。

最佳答案

在 glibc(这是 Debian 使用的 libc)中,abort 函数(它不是系统调用,它是一个普通函数)声明如下:

extern void abort (void) __THROW __attribute__ ((__noreturn__));

这个位:__attribute__ ((__noreturn__)) 是一个 gcc 扩展,告诉它函数不能返回。您的包装函数确实返回了编译器没有预料到的结果。因此它会崩溃或做一些完全意想不到的事情。

您的代码在编译时将使用 stdlib.h 中的声明来调用 abort,您提供给链接器的标志不会改变它。

Noreturn 函数的调用方式不同,编译器不必保留寄存器,它可以直接跳转到该函数而不是进行适当的调用,它甚至可能不会在它之后生成任何代码,因为根据定义,该代码不是可达。

这是一个简单的例子:

extern void ret(void);
extern void noret(void) __attribute__((__noreturn__));

void
foo(void)
{
ret();
noret();
ret();
ret();
}

编译成汇编器(即使没有优化):

$ cc -S foo.c
$ cat foo.s
[...]
foo:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
call ret
call noret
.cfi_endproc
.LFE0:
.size foo, .-foo
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-4)"
.section .note.GNU-stack,"",@progbits

请注意,有一个对 noret 的调用,但此后没有任何代码。未生成对 ret 的两次调用,并且没有 ret 指令。该功能刚刚结束。这意味着如果 noret 函数实际上由于错误(您的 abort 的实现有)而返回,则任何事情都可能发生。在这种情况下,我们将继续执行我们之后的代码段中发生的任何事情。也许是另一个函数,或者一些字符串,或者只是零,或者我们很幸运,内存映射就在这之后结束。

其实,让我们做点坏事吧。永远不要在真实代码中这样做。如果您认为这是个好主意,您需要将 key 交给您的计算机,并在保持双手举起的同时慢慢离开键盘:

$ cat foo.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void __wrap_abort(void)
{
printf("=== Abort called !=== \n");
}

int
main(int argc, char **argv)
{
abort();
return 0;
}

void
evil(void)
{
printf("evil\n");
_exit(17);
}
$ gcc -Wl,--wrap=abort -o foo foo.c && ./foo
=== Abort called !===
evil
$ echo $?
17

正如我所想,代码只是在发生在 main 之后的任何内容之后继续执行,在这个简单的示例中,编译器认为重新组织函数不是一个好主意。

关于c - 包装 abort() 系统调用时的奇怪行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39725138/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com