包装异常中止行为异常()系统调用 [英] Strange behaviour while wrapping abort() system call

查看:93
本文介绍了包装异常中止行为异常()系统调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要编写单一测试,以包装abort()系统调用.

I need, to write unitary tests, to wrap the abort() system call.

这是一段代码:

#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;
}

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

Then i compile this using the following command line :

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

运行它会给出以下输出:

Running it give the following output:

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

所以一切都很好. 现在,让我们测试相同的代码,但是不加注释地调用abort():

So everything is fine. Now let's test the same code but with abort() call uncommented:

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

我不太了解为什么在模拟abort()syscall时出现分段错误... 欢迎每一个建议!

I don't really understand why i get a segmentation fault while mocking abort() syscall... Every advice is welcome !

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

I run Debian GNU/Linux 8.5 on an x86_64 kernel. Machine is a Core i7 based laptop.

推荐答案

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

In glibc (which is the libc Debian uses) the abort function (it's not a system call, it's a normal function) is declared like this:

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

此位:__attribute__ ((__noreturn__))是gcc扩展,告诉它该函数无法返回.您的包装函数确实返回了编译器不期望的结果.因此,它会崩溃或做一些完全出乎意料的事情.

This bit: __attribute__ ((__noreturn__)) is a gcc extension that tells it that the function can't return. Your wrapper function does return which the compiler didn't expect. Because of that it will crash or do something completely unexpected.

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

Your code when compiled will be using the declarations from stdlib.h for the call to abort, the flags you gave to the linker won't change that.

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

Noreturn functions are called differently, the compiler doesn't have to preserve registers, it can just jump to the function instead of doing a proper call, it might even just not generate any code after it because that code is by definition not reachable.

这是一个简单的例子:

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

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

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

Compiled into assembler (even without optimizations):

$ 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的实现具有此错误)而返回,则可能会发生任何事情.在这种情况下,我们将继续执行紧随我们之后的代码段中的所有操作.也许是另一个函数,或者是一些字符串,或者只是零,或者也许我们很幸运,并且内存映射在此之后结束.

Notice that there is a call to noret, but there isn't any code after this. The two calls to ret were not generated and there is no ret instruction. The function just ends. This means that if the function noret actually returns because of a bug (which your implementation of abort has), anything can happen. In this case we'll just continue executing whatever happens to be in the code segment after us. Maybe another function, or some strings, or just zeroes, or maybe we're lucky and the memory mapping ends just after this.

事实上,让我们做些邪恶的事情.切勿在真实代码中执行此操作.如果您认为这是个好主意,则需要将按键移交给计算机,并在保持双手抬起的同时缓慢地离开键盘:

In fact, let's do something evil. Never do this in real code. If you ever think that this is a good idea you'll need to hand over the keys to your computer and slowly step away from the keyboard while keeping your hands up:

$ 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之后放置的所有内容,在这个简单的示例中,编译器认为重组功能不是一个好主意.

As I thought, the code just keeps going after whatever happened to be placed after main and in this simple example the compiler didn't think it would be a good idea to reorganize the functions.

这篇关于包装异常中止行为异常()系统调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆