我正在尝试让 android 上的本机崩溃报告正常工作。我使用 breakpad 创建一个转储文件,然后使用 curl 上传它(与 breakpad 附带的 minidump_upload 工具非常相似)。
为了测试,我通过尝试写入取消引用的 NULL 指针来引发段错误。
在我的转储回调函数中,在 minidump 成功创建后从 breakpad 的信号处理程序中调用,我 fork() 并运行我的上传代码。
设置 libCurl 可以正常工作,但随后它会在 curl_easy_perform 内部崩溃,并出现以下回溯(为了便于阅读,文件名被截断并删除了 10 以上的帧)。
Program received signal SIGSEGV, Segmentation fault.
0x7356d700 in dprintf_formatf (data=0x7393ba34, stream=0x65, format=0x739458a4 "l>\016", ap_save=...) at .../lib/mprintf.c:518
518 {
(gdb) bt
#0 0x7356d700 in dprintf_formatf (data=0x7393ba34, stream=0x65, format=0x739458a4 "l>\016", ap_save=...) at .../lib/mprintf.c:518
#1 0x7356e5fe in curl_mvsnprintf (buffer=0x73860540 "\b\025\206sa", maxlength=4096, format=0x737a1528 "%s; boundary=%s\r\n", ap_save=...) at .../lib/mprintf.c:932
#2 0x7355d092 in AddFormDataf (formp=0x73861780, size=0x0, fmt=0x737a1528 "%s; boundary=%s\r\n") at .../lib/formdata.c:918
#3 0x7355d606 in Curl_getformdata (data=0x75527970, finalform=0x75511ec0, post=0x75482af0, custom_content_type=0x0, sizep=0x75511ec8) at .../lib/formdata.c:1172
#4 0x73561dfc in Curl_http (conn=0x755300a0, done=0x73861928) at .../lib/http.c:1969
#5 0x7356badc in Curl_do (connp=0x75527978, done=0x73861928) at .../lib/url.c:5897
#6 0x735771f0 in multi_runsingle (multi=0x7551dfa0, now=..., data=0x75527970) at .../lib/multi.c:1218
#7 0x73577aac in curl_multi_perform (multi_handle=0x7551dfa0, running_handles=0x738619cc) at .../lib/multi.c:1714
#8 0x73571fa4 in easy_transfer (multi=0x7551dfa0) at .../lib/easy.c:759
#9 0x7357209a in easy_perform (data=0x75527970, events=false) at .../lib/easy.c:838
#10 0x735720c4 in curl_easy_perform (easy=0x75527970) at .../lib/easy.c:857
让我们分析一下我们在这里得到了什么,它在第 3 帧开始变得有趣。这里调用 AddFormDataf
- 指向表单的 NULL 指针的指针(预期,表单仍为空)
- 大小指针为 NULL
- 一个有效的格式字符串“%s;边界=%s\r\n”
- 并将两个字符串参数放入可变参数部分:“Content-Type: multipart/form-data”和“------------------------a7584d0e40b10c4a”
在第 2 帧中,一切都很好,构造了 va_list(它应该指向两个字符串中的第一个,但我不知道如何在 gdb 中调试它),并将控制权交给 curl 内部定义的 vsnprintf作为 curl_mvsnprintf。
在第一帧,所有参数看起来都还不错,4k Buffer里面有随机数据,不过应该没问题,会被覆盖,maxlength 4096是正确的,格式字符串还是我们的格式。创建 nsprintf 结构,并将控制权移交给 dprintf_formatf - 让我们打印所有传递给 dprintf_formatf 的参数:
(gdb) print info
$6 = {buffer = 0x73860540 "\b\025\206sa", length = 0, max = 4096}
(gdb) print &info
$7 = (struct nsprintf *) 0x73860510
(gdb) print addbyter
$8 = {int (int, FILE *)} 0x7356e580 <addbyter>
(gdb) print format
$9 = 0x737a1528 "%s; boundary=%s\r\n"
(gdb) print ap_save
$10 = {__ap = 0x73861554}
如您所见,info struct 已正确填充,关于指向 addbyter 函数的函数指针和 va_args ap_save 没什么好说的,但至少格式字符串仍然可以。
现在,当我们查看第 0 帧时,一切都已损坏:
#0 0x7356d700 in dprintf_formatf (data=0x7393ba34, stream=0x65, format=0x739458a4 "l>\016", ap_save=...) at .../curl/./lib/mprintf.c:518
(gdb) print ap_save
$11 = {__ap = 0x739fae60}
info 指针从 0x73860510 更改为 0x7393ba34,函数指针从 0x7356e580 更改为 0x65,格式字符串从 0x737a1528 更改为 0x739458a4,va_list 中的 __ap 从 0x73861554 更改为 0x739fae60。
这些(无效的)指针似乎在程序的每次运行中都保持不变——每当我运行程序时,我得到的数据、流和格式的地址完全相同,甚至格式的内容相同(“l>\016”)。
我有几个问题:
- 当我 fork() 时,父进程的内存被复制到新的子进程中,但是在 fork 之后父进程不能修改客户端的内存,对吗?我问是因为我的父进程是多线程的(是的,我知道,多线程和分叉,不是一个好主意,但我只是在我已经崩溃时才这样做......)
- 一个函数的所有参数都丢失并被无效的东西取代怎么会发生?我认为这里唯一的答案是某些东西破坏了堆栈,对吗?
- 什么可能会破坏我在子进程中的堆栈(这应该是一个完全健康的进程,它只拥有来自父进程的大量潜在损坏的内存)?
- 我在编译 libCurl 时可能做错了什么吗?(当从任何地方(包括分叉的子进程)调用时,整个上传工作正常,除非在信号处理程序中创建子进程的这种情况下)
- 那些无效的指针指向哪里,为什么我总是看到格式相同的字符串?我假设我在这里看到程序代码?
提前感谢您的帮助,我真的不知道如何解决这个问题:)