作者phterry (小风)
看板C_and_CPP
标题Re: [问题] Linux - Alignmet trap
时间Wed Dec 2 04:32:21 2009
※ 引述《eleghost (徵求12/5五月天DNA门票!!)》之铭言:
: 遇到的问题: (题意请描述清楚)
: 程式执行一段时间(不固定, 一分钟以下) 会出现
: Alignment trap: xxx(pid) PC=0x0001645 .....
: 来自kernel的错误讯息, 因为现在已经把成是尽量精简..
: 但还是抓不出错误, 也用 addr2line 这个程式将PC所指出的
: function address印出, 但也是没有帮助...
: 请问要怎样抓这类bug呢? 谢谢!
: 希望得到的正确结果:
: 程式跑出来的错误结果:
: 开发平台: (例: VC++ or gcc/g++ or Dev-C++, Windows or Linux)
: linux, gcc4
: 有问题的code: (请善用置底文标色功能)
: 补充说明:
PC 只是一个 Program Counter, 无法告诉你问题发生时, 执行到哪一个
function. 如果你能跑 gdb 的话, 只要问题发生时, 使用 bt (backtrace)
这个命令, 即可知道问题发生的地方在哪. 但是, 你所描述的这个问题,
只有 ARM 系列的 CPU 及 2.6.x 的 kernel 才会看得到. 通常在 embedded
device 上, 要用 gdb 来 remote debug 也不是一件简单的事.
在下提供一个方法, 可以不需要 gdb, 使程式发生问题时印出 backtrace 的
内容, 配合 addr2line 找出问题发生的地方(档案,行数,function). 但在这
之前, 可以先试着使用 gcc 的检查功能,
1. 编译时, 增加 -Wcast-align -Wpadded -Wpacked
2. 修正所有编译时出现的 Warning
如果程式码不小, 那这可能会需要相当多的时间修正, 及重新编译, 有些地方
也可能修了之後, 打坏了原本的架构, 而且, 这功能只能告诉你, "这样的程
式码, 有可能会发生这个问题", 不保证能指出真正发生问题的地方.
如果上述这个方法仍不能解决你的问题, 那麽, 後述的这个方法或许可以试试
看. (後面的内容十分冗长)
[原理概述]
我们要在程式有问题的地方, 让它中断, 然後印出 backtrace 来让 addr2line
反查. 因此, 我们分成以下三个步骤说明之.
1. 用 signal 让程式在有问题的地方中断
2. 印出 backtrace
3. 使用 addr2line 来解析其内容
[实作方法]
1. 用 signal 让程式在有问题的地方中断:
在 kernel 的文件 -- Documentation/arm/mem_align 里有提到, 当有
memory alignment 的问题发生时, kernel 会做出一些处置, 像你所看
到的讯息, 即是 kernel 印出 warning, 这个行为是可以透过 /proc
变更的. 以下的指令可以检视目前的设定,
# cat /proc/cpu/alignment
User: 0
System: 577
Skipped: 0
Half: 9076
Word: 4479
Multi: 0
User faults: 4 (signal) <----- 此为目前设定
关於 User faults, 有以下 5 种设定,
0 - ignore (预设值)
1 - warn
2 - fixup
3 - fixup+warn
4 - signal
5 - signal+warn (我们需要这个)
我们需要将行为模式设成 4 或 5, 理由是 4 和 5 都会在发生问题之时
送出 SIGBUS, 如果程式里没有 signal handler, process 就会被 kill
掉. 我们要利用这个 signal 来中断有问题的地方, 并且印出 backtrace.
因此我们先更改模式为 5,
# echo 5 > /proc/cpu/alignment
2. 印出 backtrace
这个部份, 需要利用 #include <execinfo.h> 里的 backtrace(). 如果
source code 分成很多 .c 或 .cpp 的话, 请找出 main() 所在的那个
档案来增加以下的 code,
-----------------------------------------------------------------
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
/* 此为 signal handler */
static void catch_sig(int sig) {
void *trace[128];
int n = backtrace(trace, sizeof(trace) / sizeof(trace[0]));
backtrace_symbols_fd(trace, n, 1);
exit(0);
}
/* 此函式指定 SIGBUS 的 handler 为 catch_sig() */
static void set_signals(void) {
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = catch_sig;
sigaction(SIGBUS, &act, NULL);
}
/* 在你的 main() 的前头, 呼叫 set_signals() */
int main() {
set_signals();
:
:
}
-------------------------------------------------------------------
编译时, 务必增加 -g -rdynamic 选项.
例: gcc -o prog -g -rdynamic prog.c
重新编译及执行後, 若发生 Alignment trap, kernel 会送给这个 process
一个 SIGBUS 的 signal, 而我们在 set_signal() 里, 设定了收到 SIGBUS
时要执行 catch_sig(), 而在 catch_sig() 里, 我们利用 backtrace() 来
取得 stack frame 的位址之後, 用 backtrace_symbols_fd() 来印出比较
看得懂的资讯 (但还是得配合 addr2line 来解读), 印出的内容看起来像是
以下这样:
# ./prog
./prog[0x8048743] --+---> 前面这两行必指向 catch_sig(), 可以忽略
[0xffffe420] -------+
./prog(func+0x1d)[0x804878c] --> 问题发生在这里
./prog(main+0xa3)[0x804883d] --> main() 呼叫了 func()
/lib/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7e21455]
./prog[0x8048691]
从这儿, 我们可以看到问题出现的地方在 func() 的 0x1d, 接下来, 下面
将介绍 addr2line 来更进一步解析问题出现的位置.
3. addr2line
addr2line 的用法很简单, 如下所示:
用法: addr2line -e 执行档 -f 位址
承上例:
# addr2line -e prog -f 0x804878c
func ---------------------> 函式名称
/home/haha/prog.c:312 ----> 档案及行数
最後, 只要再注意一件事即可, 就是 312 这个行数, 还不是问题发生的正
确位置, 要如何取得确切的位置呢? 假设以下内容为 prog.c 的一部份,
309 void func() {
310 unsigned char *data = (unsigned char *)malloc(16);
-> 311 struct st_bug *ptr = (struct st_bug *)data;
* 312 printf("Hi, I am here\n");
:
387 }
我们发现到, 刚才 addr2line 解析出来的行数是 312, 但是问题其实不在
312, 而是 311, 那是因为呼叫 function 时, 要把返回的位址记录在 Stack
里的缘故, 因此当程式执行到有问题的 311 行时, process 会接到 kernel
送来的 SIGBUS, 然而, 在呼叫 signal handler -- catch_sig() 前, 因为
catch_sig() 执行完後, 要回到的位址就是 312, 所以会先把 312 的位址
先存在 Stack 里, 再呼叫 catch_sig(), 而我们在 catch_sig() 里呼叫
backtrace() 时, 它只是忠实的告诉我们每一个 function 被呼叫时, Stack
里所预存的返回位址是什麽而已. (亦即每一个 stack frame 当时所纪录的
位址). 因此, addr2line 看到的行数, 需要上移一行, 才是真正问题发生的
地方. (在此例也就是指 311 行)
[此方法的其他应用]
利用"实作方法"的 2 和 3, 也可以用来捕捉 SIGSEGV (Segmentation Fault,
记忆体区段错误) 所发生的地方. 甚至是其他的 signal 发生时, 程式执行的位
置.
[後记]
本来是睡不着, 才写这篇, 结果, 写完也不用睡了.... XD
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 118.171.78.159
1F:推 cutecpu:推 (Y) 12/02 07:51
2F:推 VictorTom:太强大了, 快推....<(_ _)> 12/02 09:28
3F:推 twotwoone:辛苦了 12/02 09:53
4F:推 legnaleurc:超强! 12/02 11:06
5F:→ firose:推~ 12/02 14:42
6F:推 eleghost:太感谢了, 明天马上试试看! 12/03 21:39
7F:推 cobrasgo:看到下一篇回来推同一系列的优文 12/06 23:09