我需要,编写统一测试,包装中止()系统调用。
下面是一个代码片段:
#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
所以,一切都很好。 现在,让我们来测试相同的代码,但与中止()调用未注释:
$ ./test
pre malloc: p=(nil)
allocated 40 bytes @0x1bf2010
post malloc p=0x1bf2010
pre abort
=== Abort called !===
Segmentation fault (core dumped)
我真的不明白,为什么我得到一个分段错误而中止嘲讽()系统调用......每一个建议是欢迎!
我上的x86_64的内核上运行的Debian GNU / Linux的8.5。 机器是基于酷睿i7笔记本电脑。
在glibc的(这是libc的Debian使用)的abort
功能(这不是一个系统调用,这是一个正常的功能)可以声明如下:
extern void abort (void) __THROW __attribute__ ((__noreturn__));
该位: __attribute__ ((__noreturn__))
是gcc的一个扩展,告诉它的函数不能返回。 你的包装函数不会返回,编译器没有想到的。 正因为如此它会崩溃或者做一些完全出乎意料。
您的代码编译将使用从声明时stdlib.h
的呼叫abort
,你给的链接标志不会改变。
不返回的函数被调用不同,编译器不具有保持寄存器,它可以直接跳转到函数,而不是做一个适当的呼叫,它甚至可能只是不是因为代码是定义不可达生成后的任何代码。
这里有一个简单的例子:
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
了),任何事情都有可能发生。 在这种情况下,我们只需继续执行无论发生什么事情是在我们之后的代码段。 也许另一个功能,或者某些字符串,或者只是零,也许我们是幸运的,只是在此之后,内存映射结束。
事实上,让我们做一些邪恶的。 永远不要做这在真正的代码。 如果你认为这是你需要的钥匙交给你的计算机,并慢慢地从键盘一步之遥,同时保持你的手是一个好主意:
$ 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
在这个简单的例子,编译器不认为这将是重组的功能是一个好主意。
这是在讨论的延续艺术的回答 ,而纯粹是指作为一个实验。
不要在真正的代码,这样做!
这个问题可以用longjmp的恢复环境,呼唤真正的中止之前被避免。
下面的程序不显示未定义行为:
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>
_Noreturn void __real_abort( void ) ;
jmp_buf env ;
_Noreturn void __wrap_abort( void )
{
printf( "%s\n" , __func__ ) ;
longjmp( env , 1 ) ;
__real_abort() ;
}
int main( void )
{
const int abnormal = setjmp( env ) ;
if( abnormal )
{
printf( "saved!\n" ) ;
}
else
{
printf( "pre abort\n" ) ;
abort() ;
printf( "post abort\n" ) ;
}
printf( "EXIT_SUCCESS\n" ) ;
return EXIT_SUCCESS ;
}
输出:
pre abort
__wrap_abort
saved!
EXIT_SUCCESS
尼斯的答案,上面用汇编输出。 我有同样的问题,同样,在创建单元测试和磕碰中止()调用 - 编译器看到__noreturn__characteristic stdlib.h中,知道它可以停止呼叫到__noreturn__功能后生成的代码,但海湾合作委员会和其他编译器一样停止生成代码,即使优化抑制。 在调用存根中止()只是通过对下一功能下降后返回,声明的数据,等等。我试过--wrap办法,同上,但是__wrap_abort()返回后,调用函数就是缺少代码。
我发现覆盖这种行为的一种方法是捕捉在预处理级中止()声明 - 让您的存根中止()在一个单独的源文件,并添加到CFLAGS为的呼唤中止文件()
-D__noreturn __ = “/ * __noreturn__ * /”
这会修改stdlib.h中发现的声明的效力。 检查通过gcc的-E您的预处理器的输出,并验证这个工作。 您也可以通过objdump的的.o文件将检查的编译器的输出。
这整个的做法将具有如下其它中止生成代码源的加入副作用()调用exit()调用,和其他任何出现在与__noreturn__特征stdlib.h中,但我们大多数人没有代码随后退出(),和我们大多数人只是想清理堆栈,并从中止()调用者返回。
你可以保持连接--wrap逻辑,以调用您的__wrap_abort()调用,或者,因为你不会被调用__real_abort(),你可以做一些类似上述的东西)去你的存根中止(:
-Dabort = my_stubbed_abort
希望这可以帮助。