嵌入式系统C编程之堆栈回溯(二)
前言
本文作为《嵌入式系统C编程之堆栈回溯》的补充版。文中涉及的代码运行环境如下:
一 异常信号
信号就是软件中断,用于向正在运行的程序(进程)发送有关异步事件发生的信息。Linux应用程序发生异常时,操作系统会产生相应的信号。硬件检测到异常(非法指令、对无效的内存引用等)时也会通知内核,内核将其转换为适当的信号并发给该异常发生时正在运行的进程。 此外,进程可将信号发送给另一进程或进程组(调用kill函数),或向自身发送信号(调用raise函数)。
系统中可产生并发送多种类型的信号。在Linux环境中可通过kill -l命令查看完整的信号清单。
若进程既不忽略也未指定信号处理函数,则操作系统将对信号执行默认动作(通常是终止该进程)。可能导致进程终止的常见信号如下:
- SIGSEGV:段错误。当进程试图访问未分配给自己的内存,或对只读内存进行写操作时会产生该信号,常见编程错误有数组越界、空指针引用等。
- SIGPIPE:管道破裂。当采用管道进行进程间通信时,若读进程未打开管道或已终止就往管道写,则写进程会收到该信号。若类型为SOCK_STREAM的套接字已不再连接,则进程写到该套接字时也产生此信号。
- SIGFPE:致命的算术运算异常,如浮点或整数溢出、除0等。
- SIGBUS:由实现定义的硬件故障。其原因有:1)无效的内存地址对齐(alignment),如某些非IA-32架构要求访问四字节整数时其地址必须为4的倍数;2)不存在的物理地址,如访问映射存储区中已不存在的某个部分;3)特定的硬件故障。
- SIGILL:非法指令。当可执行文件本身出错、试图执行数据段、堆栈出错时可能产生此信号。
应用程序异常终止时,常见的故障定位方法有:1)添加打印语句二分查找,效率低下;2)gdb调试,不适用于没有gdb的环境和软件发布后;3)分析core文件。
对于导致进程终止的信号,系统默认处理是打印出错信息,并在进程当前工作目录下创建core或core.pid文件以复制该进程的存储映像。若未生成core文件,可查看ulimit -c命令结果。当结果为0时通过ulimit -c <Size>或ulimit -c unlimited命令设置core文件大小(不为0时才会产生core文件)。
在编译程序时加上-g –rdynamic选项,当进程异常终止时即可运行gdb ./<Exec> core命令,定位到出错的C语句。若程序运行环境无法运行gdb,可将core文件、程序可执行文件拷贝到支持gdb的主机上调试。
注意,某些异常信号(如SIGPIPE)不会产生core文件,而且有时core文件不能正常调试。
更好的方法是捕获到异常信号后,在信号处理函数里进行堆栈回溯,即《嵌入式系统C编程之堆栈回溯》一文所述。
二 特殊的堆栈回溯
本节主要补充两种堆栈回溯的特殊情况。
2.1 未开启-rdynamic -ldl选项
未开启-rdynamic选项时(经测-ldl选项可有可无),堆栈回溯将无法显示函数名。
例如,将Func1()函数稍作修改:
VOID Func1(VOID){
//SHOW_STACK();
CHAR *p = NULL;
*p = ;
return;
}
编译链接时开启-g选项,关闭-rdynamic和-ldl选项,执行结果如下:
Start of Stack Trace>>>>>>>>>>>>>>>>>>>>>>>>>>
Process (5663) receive signal 11
<Signal Information>:
SigNo: 11(SIGSEGV)
ErrNo: 0 (Success)
SigCode: 1
Raised at: (nil)[Unreliable]
<Register Content>:
00000033 00000000 0000007b 0000007b
00000000 00535ca0 bfda20b8 bfda20a8
0067eff4 00000000 bfda205c 00000000
0000000e 00000006 0804a1c1 00000073
00010282 bfda20a8 0000007b
<Stack Trace(Customized)>:
[] (./OmciExec) [0x0804a1c1] (<STATIC>)+0x804a1c1
[] (./OmciExec) [0x0804a1d1] (<STATIC>)+0x804a1d1
[] (./OmciExec) [0x0804a1f2] (<STATIC>)+0x804a1fb
[] (./OmciExec) [0x0804e348] (<STATIC>)+0x804e35c
[] (/lib/libc.so.6) [0x00552e9c] (__libc_start_main)+0xdc
[] (./OmciExec) [0x08049001] (<STATIC>)+0x8049001
End of Stack Trace<<<<<<<<<<<<<<<<<<<<<<<<<<<<
其中,<STATIC>应作Unknown理解,而非静态函数。
由执行结果可知,错误发生在地址0x0804a1c1处。要定位出错代码的位置,需对该程序的可执行文件进行反汇编。反汇编命令为objdump -dS --start-address 0x804a1c1 OmciExec > dump,表示使用objdump工具反汇编可执行文件OmciExec,并将从出错地址开始的结果写入dump文件。指定参数-S(隐含-d参数)时,将尽可能地反汇编出源代码,通常需结合-g编译选项。
打开dump文件,截取地址0x0804a1c1附近的指令片段如下:
VOID Func1(VOID){
//SHOW_STACK();
CHAR *p = NULL;
*p = ;
804a1c1: c6 movb $0x0,(%eax)
return;
}
VOID Func2(VOID){
Func1();
804a1cc: e8 e0 ff ff ff call 804a1b1 <Func1>
printf("%s\n", 0x123);
804a1d1: c7 movl $0x123,0x4(%esp)
return;
}
VOID BtrTest(VOID){
Func2();
804a1ed: e8 d4 ff ff ff call 804a1c6 <Func2>
printf("%d\n", /);
804a1f2: ba mov $0x5,%edx
return;
}
INT32S main(VOID)
{
BtrTest();
804e343: e8 9f be ff ff call 804a1e7 <BtrTest>
GlbOverrunTest();
804e348: e8 fe ff ff call 804e1c2 <GlbOverrunTest>
可见,函数调用顺序为Func1()->Func2()->BtrTest()->main()。
未开启-g选项时,dump结果只显示函数名和指令地址。但本例中发生的是段错误,通过重点排查出错函数内的空指针引用和数组越界代码,也可轻松定位出错语句。
2.2 忽略帧基指针
若忽略帧基指针(开启-fomit-frame-pointer),将无法正确回溯堆栈内容。此时,可借助/proc/pid/maps文件所提供的进程虚拟地址空间信息和栈顶指针SP对堆栈进行尝试性回溯。
通过cat /proc/<pid>/maps 命令可查看指定进程的内存空间分布,如:
004c0000-0057e000 rwxp 00000000 00:00 0 [heap]
2aaa8000-2aaad000 r-xp 00000000 1f:06 75 /lib/ld-uClibc-0.9.29.so
2aaad000-2aaaf000 rw-p 00000000 00:00 0
2aabc000-2aabd000 r--p 00004000 1f:06 75 /lib/ld-uClibc-0.9.29.so
2aabd000-2aabe000 rw-p 00005000 1f:06 75 /lib/ld-uClibc-0.9.29.so
… … … …
7ff9b000-7ffb0000 rwxp 00000000 00:00 0 [stack]
显示内容共有6列,分别为:
1) 地址(address):虚拟内存段的起始和终止地址,即该文件所占用的地址空间。
2) 权限(permission):虚拟内存段的访问权限(r为读,w为写,x为执行,s为共享,p为私有)。
3) 偏移量:虚拟内存段在映像文件中的偏移量。
4) 设备(device):映像文件的主设备号和次设备号(可通过cat /proc/devices查看详情)。
5) 节点(inode):映像文件的节点号(0表示没有节点与内存相对应);
6) 路径(name): 映像文件的路径名
通常权限为r-xp时对应代码段(正文段),权限为rw-p时对应数据段。
因此,忽略帧基指针时,函数堆栈回溯的步骤如下(以Intel x86架构为例):
1) 读取/proc/<pid>/maps文件,记录映射到进程虚拟地址空间的可执行代码段的起止位置;
2) 从当前栈顶指针SP出发,向高地址依次取出一个整型值。若该值位于上步所计算的某个地址区间中,则输出该值(可能是函数栈帧中的返回地址,即调用指令的下条地址);
3) 循环步骤2,直至读取完指定数目的整型值。
本实现首先需要增加几个宏定义,如下:
#if defined(REG_RIP)
#define REG_IP REG_RIP //指令指针(保存返回地址)
#define REG_BP REG_RBP //帧基指针
#define REG_SP REG_RSP //栈顶指针
#define REG_FMT "%016lx"
#elif defined(REG_EIP)
#define REG_IP REG_EIP
#define REG_BP REG_EBP
#define REG_SP REG_ESP
#define REG_FMT "%08x"
#else
#warning "Neither REG_RIP nor REG_EIP is defined!"
#define REG_FMT "%08x"
#endif #ifndef TASK_SIZE //用户进程空间大小(基于该值可确定堆栈底部)
#define TASK_SIZE (0xbf000000UL)
#endif #ifndef MAPS_SEG_NUM //解析'/proc/pid/maps'结果时的最大段数(条目数)
#define MAPS_SEG_NUM 30
#endif
此处TASK_SIZE定义为PAGE_OFFSET(0xc0000000)向低地址偏移16M处,即用户进程空间可用的虚拟内存范围为0~0xbf000000。
然后定义ShowStackContent()函数如下:
/******************************************************************************
* 函数名称: ShowStackContent
* 功能说明: 显示堆栈内容
******************************************************************************/
static VOID ShowStackContent(INT32U dwStkPtr)
{
fprintf(gpStraceFd, "<Current Thread Maps>:\n");
CHAR szMapsCmd[sizeof("/proc/65535/maps")] = {};
sprintf(szMapsCmd, "/proc/%d/maps", getpid());
FILE *pFile = fopen(szMapsCmd, "r");
if(NULL == pFile)
{
fprintf(gpStraceFd, "Open File '%s' Error(%s)!\n", szMapsCmd, strerror(errno));
return;
} INT32U dwSegIdx = , dwSegNum = ;
INT32U dwStartAddr = , dwEndAddr = ;
INT32U aAddrSeg[MAPS_SEG_NUM*] = {};
CHAR szMapsBuf[] = {};
while(fgets(szMapsBuf, sizeof(szMapsBuf)-, pFile) != NULL)
{
CHAR cAccess;
CHAR szMisc[]; //杂项,不必关注
INT32S dwRet = sscanf(szMapsBuf, "%08x-%08x %*c%*c%c %[^\n]%*c", &dwStartAddr, &dwEndAddr, &cAccess, szMisc);
if(- == dwRet || == dwRet)
break; if(cAccess == 'x'/*Executable*/ && dwSegIdx < MAPS_SEG_NUM* && dwEndAddr != TASK_SIZE)
{
fprintf(gpStraceFd, "\t%s", szMapsBuf);
aAddrSeg[dwSegIdx++] = dwStartAddr;
aAddrSeg[dwSegIdx++] = dwEndAddr;
}
}
dwSegNum = dwSegIdx;
fclose(pFile); //从当前ESP出发检查高地址处的dwDwordNum个堆栈单位(双字)
INT32U dwDwordNum = ((TASK_SIZE-dwStkPtr) > ) ? : (TASK_SIZE-dwStkPtr);
dwDwordNum >>= ;
fprintf(gpStraceFd, "<Possible Call Trace>:\n\t"); INT32U dwIdx = , dwIdx2 = ;
for(; dwIdx < dwDwordNum; dwIdx++)
{
INT32U dwStkCont = *((INT32U*)dwStkPtr + dwIdx);
for(dwSegIdx = ; dwSegIdx < dwSegNum; dwSegIdx+=)
{
if(dwStkCont >= aAddrSeg[dwSegIdx] && dwStkCont <= aAddrSeg[dwSegIdx+])
{
fprintf(gpStraceFd, "[%8x] ", dwStkCont);
if( == ((++dwIdx2)%)) //每行输出4个堆栈内容
fprintf(gpStraceFd, "\n\t");
break;
}
}
}
fprintf(gpStraceFd, "\n");
return;
}
SigHandler()函数输出Customized堆栈回溯(仅首行有参考意义)后,再调用ShowStackContent()函数:
ShowStackContent(ptContext->uc_mcontext.gregs[REG_SP]);
以2.1节Func1()函数为例。编译链接时开启-g和-fomit-frame-pointer选项,可选关闭-rdynamic和-ldl选项,执行结果如下:
Start of Stack Trace>>>>>>>>>>>>>>>>>>>>>>>>>>
Process (15207) receive signal 11
<Signal Information>:
SigNo: 11(SIGSEGV)
ErrNo: 0 (Success)
SigCode: 1
Raised at: (nil)[Unreliable]
<Register Content>:
00000033 00000000 0000007b 0000007b
00000000 00535ca0 bfe17428 bfe1735c
0067eff4 00000001 bfe173d0 00000000
0000000e 00000006 0804a373 00000073
00010286 bfe1735c 0000007b
<Stack Trace(Customized)>:
[] (./OmciExec) [0x0804a373] (<STATIC>)+0x804a373
[] (./OmciExec) [0x08049001] (<STATIC>)+0x8049001
<Current Thread Maps>:
0051b000-00535000 r-xp 00000000 fd:00 28871142 /lib/ld-2.5.so
00535000-00536000 r-xp 00019000 fd:00 28871142 /lib/ld-2.5.so
00536000-00537000 rwxp 0001a000 fd:00 28871142 /lib/ld-2.5.so
0053d000-0067c000 r-xp 00000000 fd:00 28871143 /lib/libc-2.5.so
0067c000-0067d000 --xp 0013f000 fd:00 28871143 /lib/libc-2.5.so
0067d000-0067f000 r-xp 0013f000 fd:00 28871143 /lib/libc-2.5.so
0067f000-00680000 rwxp 00141000 fd:00 28871143 /lib/libc-2.5.so
00680000-00683000 rwxp 00680000 00:00 0
00685000-006aa000 r-xp 00000000 fd:00 28871150 /lib/libm-2.5.so
006aa000-006ab000 r-xp 00024000 fd:00 28871150 /lib/libm-2.5.so
006ab000-006ac000 rwxp 00025000 fd:00 28871150 /lib/libm-2.5.so
006ae000-006b0000 r-xp 00000000 fd:00 28871144 /lib/libdl-2.5.so
006b0000-006b1000 r-xp 00001000 fd:00 28871144 /lib/libdl-2.5.so
006b1000-006b2000 rwxp 00002000 fd:00 28871144 /lib/libdl-2.5.so
006b4000-006c8000 r-xp 00000000 fd:00 28871145 /lib/libpthread-2.5.so
006c8000-006c9000 r-xp 00013000 fd:00 28871145 /lib/libpthread-2.5.so
006c9000-006ca000 rwxp 00014000 fd:00 28871145 /lib/libpthread-2.5.so
006ca000-006cc000 rwxp 006ca000 00:00 0
00a68000-00a6f000 r-xp 00000000 fd:00 28871146 /lib/librt-2.5.so
00a6f000-00a70000 r-xp 00006000 fd:00 28871146 /lib/librt-2.5.so
00a70000-00a71000 rwxp 00007000 fd:00 28871146 /lib/librt-2.5.so
00b13000-00b42000 r-xp 00000000 fd:00 4096074 /usr/lib/libreadline.so.5.1
00b42000-00b46000 rwxp 0002f000 fd:00 4096074 /usr/lib/libreadline.so.5.1
00b46000-00b47000 rwxp 00b46000 00:00 0
00d10000-00d11000 r-xp 00d10000 00:00 0 [vdso]
04e6a000-04eaa000 r-xp 00000000 fd:00 22226947 /usr/lib/libncurses.so.5.5
04eaa000-04eb2000 rwxp 00040000 fd:00 22226947 /usr/lib/libncurses.so.5.5
04eb2000-04eb3000 rwxp 04eb2000 00:00 0
08048000-08052000 r-xp 00000000 08:11 86278170 /sdb1/wangxiaoyuan/linux_test/DCLinkedList/OmciExec
<Possible Call Trace>:
[] [ 804a382] [ 804a3a2] [ 804eac1]
[ 67eff4] [ 67d204] [ 804f2e9] [ 568e25]
[ 67eff4] [] [ 804f2d0] [ 552e9c]
[ 535ca0] [ 804f2d0] [ 552e9c] []
[ 67eff4] [ 535ca0] [ 52e4f0] [ 552dcd]
[ 535fc0] [ 8048fe0] [] [ 804eaae]
[ 804f2d0] [ 804f2c0] [] [ 53202b]
[ d10400] [ d10000] [] [ 8048fe0] End of Stack Trace<<<<<<<<<<<<<<<<<<<<<<<<<<<<
通过objdump -dS OmciExec > dump命令反汇编可执行文件OmciExec。
打开dump文件,根据<Stack Trace(Customized)>首行的返回地址和<Possible Call Trace>的堆栈内容,分析和摘取位于OmciExec内存段的地址,匹配dump文件中的指令地址(若匹配极有可能为出错代码的下条指令)。
截取部分指令片段如下:
VOID Func1(VOID){
//SHOW_STACK();
CHAR *p = NULL;
*p = ;
804a373: c6 movb $0x0,(%eax)
return;
}
VOID Func2(VOID){
Func1();
804a37d: e8 e2 ff ff ff call 804a364 <Func1>
printf("%s\n", 0x123);
804a382: c7 movl $0x123,0x4(%esp)
return;
}
VOID BtrTest(VOID){
Func2();
804a39d: e8 d8 ff ff ff call 804a37a <Func2>
printf("%d\n", /);
804a3a2: ba mov $0x5,%edx
return;
}
INT32S main(VOID)
{
BtrTest();
804eabc: e8 d9 b8 ff ff call 804a39a <BtrTest>
GlbOverrunTest();
804eac1: e8 8a fe ff ff call 804e950 <GlbOverrunTest>
可见,函数调用顺序为Func1()->Func2()->BtrTest()->main()。
注意,当出错语句调用库函数时,本实现很难有效地回溯。例如,删去Func1()函数中对*p的赋值语句,执行结果如下所示:
Start of Stack Trace>>>>>>>>>>>>>>>>>>>>>>>>>>
Process (28854) receive signal 11
<Signal Information>:
SigNo: 11(SIGSEGV)
ErrNo: 0 (Success)
SigCode: 1
Raised at: 0x123[Unreliable]
<Register Content>:
00000033 00000000 0000007b 0000007b
00000123 bfff6004 bfff5fdc bfff59bc
0067eff4 00579999 00000003 00000123
0000000e 00000004 005ad1ab 00000073
00010206 bfff59bc 0000007b
<Stack Trace(Customized)>:
[] (/lib/libc.so.6) [0x005ad1ab] (strlen)+0x0b
[] (/lib/libc.so.6) [0x00582e83] (_IO_printf)+0x33
[] (./OmciExec) [0x0804a381] (<STATIC>)+0x804a381
[] (./OmciExec) [0x08049001] (<STATIC>)+0x8049001
<Current Thread Maps>:
003bb000-003bc000 r-xp 003bb000 00:00 0 [vdso]
0051b000-00535000 r-xp 00000000 fd:00 28871142 /lib/ld-2.5.so
00535000-00536000 r-xp 00019000 fd:00 28871142 /lib/ld-2.5.so
00536000-00537000 rwxp 0001a000 fd:00 28871142 /lib/ld-2.5.so
0053d000-0067c000 r-xp 00000000 fd:00 28871143 /lib/libc-2.5.so
0067c000-0067d000 --xp 0013f000 fd:00 28871143 /lib/libc-2.5.so
0067d000-0067f000 r-xp 0013f000 fd:00 28871143 /lib/libc-2.5.so
0067f000-00680000 rwxp 00141000 fd:00 28871143 /lib/libc-2.5.so
00680000-00683000 rwxp 00680000 00:00 0
00685000-006aa000 r-xp 00000000 fd:00 28871150 /lib/libm-2.5.so
006aa000-006ab000 r-xp 00024000 fd:00 28871150 /lib/libm-2.5.so
006ab000-006ac000 rwxp 00025000 fd:00 28871150 /lib/libm-2.5.so
006ae000-006b0000 r-xp 00000000 fd:00 28871144 /lib/libdl-2.5.so
006b0000-006b1000 r-xp 00001000 fd:00 28871144 /lib/libdl-2.5.so
006b1000-006b2000 rwxp 00002000 fd:00 28871144 /lib/libdl-2.5.so
006b4000-006c8000 r-xp 00000000 fd:00 28871145 /lib/libpthread-2.5.so
006c8000-006c9000 r-xp 00013000 fd:00 28871145 /lib/libpthread-2.5.so
006c9000-006ca000 rwxp 00014000 fd:00 28871145 /lib/libpthread-2.5.so
006ca000-006cc000 rwxp 006ca000 00:00 0
00a68000-00a6f000 r-xp 00000000 fd:00 28871146 /lib/librt-2.5.so
00a6f000-00a70000 r-xp 00006000 fd:00 28871146 /lib/librt-2.5.so
00a70000-00a71000 rwxp 00007000 fd:00 28871146 /lib/librt-2.5.so
00b13000-00b42000 r-xp 00000000 fd:00 4096074 /usr/lib/libreadline.so.5.1
00b42000-00b46000 rwxp 0002f000 fd:00 4096074 /usr/lib/libreadline.so.5.1
00b46000-00b47000 rwxp 00b46000 00:00 0
04e6a000-04eaa000 r-xp 00000000 fd:00 22226947 /usr/lib/libncurses.so.5.5
04eaa000-04eb2000 rwxp 00040000 fd:00 22226947 /usr/lib/libncurses.so.5.5
04eb2000-04eb3000 rwxp 04eb2000 00:00 0
08048000-08052000 r-xp 00000000 08:11 86278170 /sdb1/wangxiaoyuan/linux_test/DCLinkedList/OmciExec
<Possible Call Trace>:
[ 57cc0e] [ 804faba] [ 5245b5] [ 54ecd0]
[ 51b5c6] [ 80486fe] [ 578f5f] [ 52498d]
[ 528e66] [ 53d1a4] [ 52f5d1] [ 67f554]
[ 540c24] [ 540bf0] [ 804fabb] [ 804faba]
[ 529a29] [ 6b7382] [ 53d120] [ 528e49]
[ 540c24] [ 53fad8] [ 6b6c54] []
[ 6b4fa8] [ 535fc0] [ 524aa7] [ 6b4fa8]
[ 5367b4] [] [ 804faba] [ 535fc0]
[ 5496c4] [ 5278b5] [ 535fc0] [ 6b6c54]
[] [ 5245b5] [ 54ecd0] [ 6b73aa]
[ 529a29] [ 6b7382] [ 535fc0]
End of Stack Trace<<<<<<<<<<<<<<<<<<<<<<<<<<<<
可见,最内层的出错代码位于libc共享库内的strlen函数处。该库编译时未忽略帧基指针,故可正确回溯strlen和_IO_printf(printf别名)函数。但printf函数占用较大的堆栈空间,且<Possible Call Trace>显示的堆栈内容有限,因此无法进一步回溯。
由<Stack Trace(Customized)>第三行回溯信息可知,OmciExec内存段代码出错时返回地址为0x0804a381。反汇编可执行文件OmciExec后,截取部分指令片段如下:
VOID Func2(VOID){
Func1();
printf("%s\n", 0x123);
804a375: c7 ba fa movl $0x804faba,(%esp)
804a37c: e8 cf ea ff ff call 8048e50 <printf@plt>
return;
}
804a381: c4 0c add $0xc,%esp
804a384: c3 ret
可知,出错代码为printf("%s\n", 0x123)语句,这也与<Signal Information>中的" Raised at: 0x123"相吻合。
嵌入式系统C编程之堆栈回溯(二)的更多相关文章
- 嵌入式系统C编程之堆栈回溯【转】
转自:https://www.cnblogs.com/clover-toeic/p/3949896.html 前言 在嵌入式系统C语言开发调试过程中,常会遇到各类异常情况.一般可按需添加打印信息,以便 ...
- 嵌入式系统C编程之堆栈回溯
前言 在嵌入式系统C语言开发调试过程中,常会遇到各类异常情况.一般可按需添加打印信息,以便观察程序执行流或变量值是否异常.然而,打印操作会占用CPU时间,而且代码中添加过多打印信息时会显得很凌乱.此外 ...
- 嵌入式系统C编程之错误处理【转】
转自:http://www.cnblogs.com/clover-toeic/p/3919857.html 前言 本文主要总结嵌入式系统C语言编程中,主要的错误处理方式.文中涉及的代码运行环境如下: ...
- 嵌入式系统C编程之错误处理
前言 本文主要总结嵌入式系统C语言编程中,主要的错误处理方式.文中涉及的代码运行环境如下: 一 错误概念 1.1 错误分类 从严重性而言,程序错误可分为致命性和非致命性两类.对于致命性错误,无法执行 ...
- C语言嵌入式系统编程修炼
C语言嵌入式系统编程修炼 2008-08-19 作者:宋宝华 来源:天极网 C语言嵌入式系统编程修炼之背景篇 本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的C语言编程 ...
- [读书笔记1]《C语言嵌入式系统编程修炼》
大学前两年一直搞的是单片机,写的是嵌入式C语言程序,走过了不少弯路,现在感觉仍然在走弯路.有幸偶尔看到了这篇文章,深感自己以前写程序的时候存在很多误区.现写篇博客做下总结. 作者:宋宝华出处:天极 ...
- C语言嵌入式系统编程修炼之六:性能优化
使用宏定义 在C语言中,宏是产生内嵌代码的唯一方法.对于嵌入式系统而言,为了能达到性能要求,宏是一种很好的代替函数的方法. 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的 ...
- C语言嵌入式系统编程修炼之三:内存操作
数据指针 在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力.在嵌入式系统的实际调试中,多借助C语言指针所具 ...
- C语言嵌入式系统编程修炼之一:背景篇
不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑,汇编语言具备这样的特质.但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发 ...
随机推荐
- Maven项目对象模型(POM)
Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. Maven 除了以程序构建能力为特色之外,还提供高级项目管理工具.由于 Maven 的缺省构建 ...
- 使用ConcurrentLinkedQueue惨痛的教训【转】
转自:http://blog.csdn.net/jackpk/article/details/49634577 服务端原本有个定时任务对一个集合ArrayList 中的消息做处理. 因为考虑到处理消息 ...
- SpringBoot学习:使用spring-boot-devtools进行热部署
项目下载地址:http://download.csdn.net/detail/aqsunkai/9805821 pom.xml添加依赖: <!--支持热启动jar包--> <depe ...
- c# winform 获取当前程序运行根目录,winform 打开程序运行的文件夹
// 获取程序的基目录. System.AppDomain.CurrentDomain.BaseDirectory // 获取模块的完整路径. System.Diagnostics.Process.G ...
- Redis 缓存 + Spring 的集成示例(转载)
1. 依赖包安装 pom.xml 加入: <dependency> <groupId>org.springframework.data</groupId> < ...
- Enhance基本例子
太晚了,有些东西没有补充,回头再补上. 先上Demo 1.要执行的方法 package enhancerTest; /** * Created by LiuSuSu on 2017/3/26. */ ...
- FXAA
无抗锯齿 SSAA 硬件抗锯齿,OpenGL自带,4x FXAA 从图中可以看出 FXAA抗锯齿,没有硬件MSAA抗锯齿效果好
- Unreal发展史
Unreal发展史 引子 四年前的一个深夜,或者说是一个早晨,Unreal的传奇开始了.它发生在马里兰州一个不起眼的市镇Rockvill,在一套公寓大楼里回响起一支墨西哥流浪乐队的曲子,那里住着Epi ...
- BarTender复合条形码中的分隔符模式详解
在BarTender 10.1中,支持使用BarTender分隔符模式的复合条形码符号体系包括GS1 Composite和GS1 DataBar (RSS).本文小编给大家详细讲解BarTender分 ...
- 多页Excel转换成PDF时如何保存为单独文件
通过ABBYY PDF Transformer+图文识别软件,使用PDF-XChange打印机将多页Excel工作簿转换成PDF文档(相关文章请参考ABBYY PDF Transformer+从MS ...