博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C语言 GCC编译的程序运行报错 malloc.c:2401: sysmalloc: Assertion 的分析解决
阅读量:4303 次
发布时间:2019-05-27

本文共 5623 字,大约阅读时间需要 18 分钟。

问题背景

最近在写项目,在涉及到内存分配时报标题中错误。该错误有以下两点神奇的特征:

  1. MacOS下用clang编译后运行完全正常
  2. Ubuntu下用gcc编译后运行出上述断言错,但是在出错位置附近加puts("任意内容")后,运行完全正常

错误分析

因为出错位置附近加puts("任意内容")后,运行完全正常,且MacOSclang编译后一切正常,初步推测该错误是由编译器不同引发。又由于断言在malloc,该错误必定与内存分配有关。由于问题代码段在添加puts等输出语句后问题消失,因此很难通过插入puts的方法检测问题发生位置。不过在仔细检查代码后,终于将错误定位到了的get_pb函数上。仔细阅读该函数以及相关结构体定义,错误原因终于水落石出:

simple_protobuf.h

...struct SIMPLE_PB {
uint32_t struct_len, real_len; char target[];};typedef struct SIMPLE_PB SIMPLE_PB;...

protobuf.c

...SIMPLE_PB* spb = malloc(struct_len + sizeof(uint32_t));...

原来,错误出在内存分配少了。可是为何mac下程序一切正常,在加入puts后ubuntu下也正常了呢?

问题解释

1. 为何mac下程序一切正常

首先介绍以下两条规则:

  1. 64位系统下,内存单元一定以8字节对齐
  2. malloc分配空间不满8字节的倍数时,自动补齐。

内存的对齐是由空间换效率的方法。

那么,上面的代码中假如struct_len是8的倍数,加上sizeof(uint32_t)==4,就必然不是8的倍数了。此时malloc自动再加4字节补齐,恰好等于了struct SIMPLE_PB 中的两个成员uint32_t struct_len, real_len的空间。

  1. 上面的这两条是经过简化的
  2. 由于内存的设计考量,实际上64位系统分配内存块是以16字节(128位)对齐
  3. malloc分配的内存还有overhead信息(32字节),如果损坏该信息将报其它断言错
  4. 错误代码的情况中,struct_len=64+4=68字节,加上4后实际传给malloc是72字节,不满16的倍数,补为80字节(不含overhead

因此,在没有任何检查的情况下,由于空间足够,程序应当运行正常。很显然gcc在这里采用了断言进行了检查,因而报错,帮助我们发现了这个隐藏的错误。

2. 为何在加入puts后ubuntu下也正常

由于与编译器优化有关,我们只能从汇编代码上寻找原因了。

将问题代码单独提取出来,形成如下代码

#include 
#include
#include
#include
struct SIMPLE_PB {
uint32_t struct_len, real_len; char target[];};typedef struct SIMPLE_PB SIMPLE_PB;struct DAT {
char n[64]; uint32_t c;};typedef struct DAT DAT;static uint32_t read_num(FILE* fp) {
uint8_t c; uint32_t n = 0; uint8_t i = 0; do {
c = fgetc(fp); if(feof(fp)) return n; else n |= (c & 0x7f) << (7 * i++); } while((c & 0x80)); return n;}SIMPLE_PB* get_pb(FILE* fp) {
uint32_t init_pos = ftell(fp); uint32_t struct_len = read_num(fp); if(struct_len > 1) {
SIMPLE_PB* spb = malloc(struct_len + sizeof(uint32_t)); if(spb) {
spb->struct_len = struct_len; spb->real_len = 0; char* p = spb->target; char* end = p + struct_len; memset(p, 0, struct_len); while(p < end) {
uint32_t offset = read_num(fp); uint32_t data_len = read_num(fp); if(data_len > 0) fread(p, data_len, 1, fp); p += offset; } spb->real_len = ftell(fp) - init_pos; return spb; } } return NULL;}int main(){
SIMPLE_PB* spb = get_pb(fopen("dat.sp", "rb")); DAT* d = (DAT*)spb->target; printf("%d %d %s %u\n", spb->struct_len, spb->real_len, d->n, d->c); return 0; }

1. 在Mac下编译

clang test.c -O3 -o test./test68 13 fumiama 9

结果一切正常。

2. 在Ubuntu下编译

gcc -O3 test.c -o test./test test: malloc.c:2401: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.Aborted (core dumped)

错误出现。

查看汇编代码如下:

gcc -O3 -S test.c -o test.s

下面仅列出关键代码

.L2:	cmpl	$1, %r12d		; 比较struct_len	jbe	.L6					; if(struct_len <= 1) return NULL	movl	%r12d, %ebp		; ebp = struct_len	leaq	4(%rbp), %rdi	; 明确加了4	call	malloc@PLT		; 调用实际按8字节对齐	testq	%rax, %rax		; 比较spb	movq	%rax, 16(%rsp)	; 将分配的指针放入内存	je	.L6					; if(!spb) return NULL	leaq	8(%rax), %r13	; r13 = rax + 8指向了spb->target(char* p = spb->target)	movl	%r12d, (%rax)	; spb->struct_len = struct_len	movl	$0, 4(%rax)		; spb->real_len = 0(实际无法访问区域)	xorl	%esi, %esi		; esi = 0	movq	%rbp, %rdx		; rdx = struct_len	leaq	0(%r13,%rbp), %rax	; char* end = p + struct_len	movq	%r13, %rdi		; rdi = p	movq	%rax, %r15		; r15 = end	movq	%rax, 8(%rsp)	; 保存end备用	call	memset@PLT	cmpq	%r15, %r13		; 比较p与end	jnb	.L7					; p>=end退出循环	.p2align 4,,10			; 进入while循环...	.p2align 3

分析后发现,这段代码完全没有问题,也没有执行任何边界检查,因此问题并非出自这里。

使用gdb调试后,发现问题出在printf调用处:
gdb
也就是说,这个问题平时并不会出现,只有在调用printf函数时,其内置的边界检测才会报错!
那么,让我们再来分析一下调用printf时的汇编代码:

.LC0:	.string	"rb".LC1:	.string	"dat.sp".LC2:	.string	"%d %d %s %u\n"....main:.LFB54:	.cfi_startproc	leaq	.LC0(%rip), %rsi	leaq	.LC1(%rip), %rdi	subq	$8, %rsp	.cfi_def_cfa_offset 16	call	fopen@PLT			; 调用fopen	movq	%rax, %rdi	call	get_pb				; 调用get_pb	movl	4(%rax), %ecx		; ecx = real_len	movl	72(%rax), %r9d		; r9d = d->c	leaq	8(%rax), %r8		; r8 = n	movl	(%rax), %edx		; edx = struct_len	leaq	.LC2(%rip), %rsi	; rsi = &"%d %d %s %u\n"	movl	$1, %edi			; edi = 1	xorl	%eax, %eax			; eax = 0	call	__printf_chk@PLT	; 有检查的printf	xorl	%eax, %eax			; return 0	addq	$8, %rsp	.cfi_def_cfa_offset 8	ret	.cfi_endproc

可见入参一切正常,在gdb中进一步做断点,发现在执行printf前,求值也没有任何问题,下面截图中的代码甚至将每个变量都分离表示然后传入printf,但是仍然触发了断言。

执行前
执行后

基于此,判断并不是printf本身的问题,再结合断言由malloc发出,因此尝试在printf前加一条malloc语句:

malloc(4);printf("%d %d %s %u\n", spb->struct_len, spb->real_len, d->n, d->c);

果然,程序执行到malloc就已经报相同错误。

到这里已经可以确定问题是memory corruption,下面让就我们来复现添加puts运行成功的场景:

puts("Magic!");SIMPLE_PB* spb = malloc(struct_len + sizeof(uint32_t));

于是,错误奇迹般地消失了:

noerr
那么让我们来看看加了puts之后的汇编代码:

.LC0:	.string	"Magic!"	.text....L2:	cmpl	$1, %r12d	jbe	.L6	leaq	.LC0(%rip), %rdi	movl	%r12d, %ebp	call	puts@PLT	leaq	4(%rbp), %rdi	call	malloc@PLT...

可以推测,puts申请并释放了一片内存,使后续的printf可以直接使用puts释放的内存块而无需经过内存完整性检测,因而调用成功。

具体来说,该内存块需要满足以下条件:

  1. 刚刚被释放,未挪作他用
  2. 大小恰与printf需要分配的块相同

但是,我们并不知道这个大小具体是多少,也不知道其申请的内存块是否唯一。实际上,使用malloc(BUFSIZ)申请一片内存并释放后,断言仍然会出现。那么,为了验证我们的构想,只有遍历所有可能的情况了。

于是,编写fake_puts函数如下:

void fake_puts() {
for(int i = 0; i < BUFSIZ; i++) {
void* p = malloc(i); free(p); }}

使用该函数替换puts

fake_puts();SIMPLE_PB* spb = malloc(struct_len + sizeof(uint32_t));

禁用gcc优化后编译运行,果然消除错误。

fake

解决方案

当然,要解决这个错误,只需要分配够空间即可。

SIMPLE_PB* spb = malloc(struct_len + 2 * sizeof(uint32_t));

附录

这里提供程序用到的文件dat.sp的编码,有兴趣的读者可以自己解码验证上述程序。

弐乶柕筩晛搐帄圀㴆

转载地址:http://uhmws.baihongyu.com/

你可能感兴趣的文章
docker-daemon.json各配置详解
查看>>
Docker(一)使用阿里云容器镜像服务
查看>>
Docker(二) 基础命令
查看>>
Docker(三) 构建镜像
查看>>
Spring 全家桶注解一览
查看>>
JDK1.8-Stream API使用
查看>>
cant connect to local MySQL server through socket /tmp/mysql.sock (2)
查看>>
vue中的状态管理 vuex store
查看>>
Maven之阿里云镜像仓库配置
查看>>
Maven:mirror和repository 区别
查看>>
微服务网关 Spring Cloud Gateway
查看>>
SpringCloud Feign的使用方式(一)
查看>>
SpringCloud Feign的使用方式(二)
查看>>
关于Vue-cli+ElementUI项目 打包时排除Vue和ElementUI
查看>>
Vue 路由懒加载根据根路由合并chunk块
查看>>
vue中 不更新视图 四种解决方法
查看>>
MySQL 查看执行计划
查看>>
OpenGL ES 3.0(四)图元、VBO、VAO
查看>>
OpenGL ES 3.0(五)纹理
查看>>
OpenGL ES 3.0(八)实现带水印的相机预览功能
查看>>