ARM e7f000f0 udf 指令异常

最近遇到一个程序崩溃,并不常见的SIGILL。 debug之。

Core was generated by `/usr/bin/xxxxx'.
Program terminated with signal SIGILL, Illegal instruction

gdb查看,崩溃在了free函数

(gdb) disassemble 
Dump of assembler code for function free:
   0xb6f408f0 <+0>:	cmp	r0, #0
   0xb6f408f4 <+4>:	bxeq	lr
   0xb6f408f8 <+8>:	ldr	r3, [r0, #-4]
   0xb6f408fc <+12>:	push	{r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0xb6f40900 <+16>:	tst	r3, #1
   0xb6f40904 <+20>:	bic	r8, r3, #1
   0xb6f40908 <+24>:	sub	sp, sp, #20
   0xb6f4090c <+28>:	sub	r4, r0, #8
   0xb6f40910 <+32>:	bne	0xb6f40938 <free+72>
   0xb6f40914 <+36>:	ldr	r3, [r0, #-8]
   0xb6f40918 <+40>:	sub	r0, r4, r3
   0xb6f4091c <+44>:	tst	r3, #1
   0xb6f40920 <+48>:	add	r1, r8, r3
   0xb6f40924 <+52>:	beq	0xb6f4092c <free+60>
=> 0xb6f40928 <+56>:	udf	#0
   0xb6f4092c <+60>:	add	sp, sp, #20
   0xb6f40930 <+64>:	pop	{r4, r5, r6, r7, r8, r9, r10, r11, lr}
   0xb6f40934 <+68>:	b	0xb6f5c620 <__munmap>

free函数里的确有udf指令

查看musk的free函数实现

void free(void *p)
{
	struct chunk *self = MEM_TO_CHUNK(p);
	struct chunk *next;
	size_t final_size, new_size, size;
	int reclaim=0;
	int i;

	if (!p) return;

	if (IS_MMAPPED(self)) {
		size_t extra = self->psize;
		char *base = (char *)self - extra;
		size_t len = CHUNK_SIZE(self) + extra;
		/* Crash on double free */
		if (extra & 1) a_crash();
		__munmap(base, len);
		return;
	}
static inline void a_crash()
{
	__asm__ __volatile__(
#ifndef __thumb__
		".word 0xe7f000f0"
#else
		".short 0xdeff"
#endif
		: : : "memory");
}

当musl发现内存可能会double free的情况下,会主动调用a_crash(),即插入一条arm的udf指令。 主动trap程序crash。

其实想想这样也比较科学,避免一些allocator实现下因为double free导致内存链表trash的问题。往往double free的地方没事。程序会崩在十万八千里开外的地方。debug起来相当痛苦。

在google这个问题的同时,发现了另一个有趣的问题:gcc编译器也会主动插入udf指令

这是一个论坛上别人遇到的问题


问题
       C语言编写的,判断从nor启动还是nand启动的函数在编译链接时,函数并没有完全编译, 反汇编结果如下(红色字体部分), 请老师帮忙看看是什么原因, 谢谢!

c函数如下:

int isBootFromNorFlash(void)
{
        volatile int *p = (volatile int *)0;
        int val;

        val = *p;
        *p = 0x12345678;

        if (*p == 0x12345678)
        {
                *p = val;
                puts("boot from nor.\n\r");
                return 0;
        }
        else
        {
                puts("boot from nand.\n\r");
                return 1;
        }

}


反汇编结果如下,红色字体部分,只编译了前面几行命令,后面都没有了。


33f80304 <isBootFromNorFlash>:
33f80304:        e3a03000         mov        r3, #0
33f80308:        e5933000         ldr        r3, [r3]
33f8030c:        e7f000f0         udf        #0

实际上这是arm gcc的一个特性,当检测到undefined behavior的时候,会主动插入udf指令来trap异常。

GCC6以后可以通过 -Wnull-dereference  在编译的时候检测该错误。

ktkachov 2017-07-27 09:28:26 UTC

That inst is the trap instruction that is inserted by the isolate-paths pass because it detects undefined behaviour.

While the result of calling data() is unspecified, this is not what the testcase is doing. It's using operator[] which performs an access to the underlying storage directly and is thus out of bounds and undefined

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81580

猜你喜欢

转载自blog.csdn.net/thwack/article/details/82147869