编程系统

Posted by wsxq2 on 2022-03-16
TAGS:  FROM_DOCX编程系统C语言编译汇编GDB调试构建系统二进制

本文最后一次编辑时间:2022-03-16 15:34:45 +0800

本文目的

涉及计算机编程的所有相关内容。以C语言为核心。包括C语言相关知识、编译过程、汇编、运行过程、GDB调试技巧等内容。是对C语言笔记的补充

概念区分

暂无

C语言

遇到过的问题

全局变量是否应该初始化

全局变量默认初始为0,但是需要注意的是,如果有两个地方定义的同名的全局变量,但一边手动初始化,另一边没有手动初始化,编译时却不会报错。如果希望报错,需要给编译器加选项-fno-common。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* module_a.c */
#include <stdio.h>

void function(void);

int global = 0;

int main()
{
    global = 3;
    function();
    printf("main: %d \n", global);
    return 0;
}
/* module_b.c */
#include <stdio.h>

int global;

void function(void)
{
    global = 6;
    printf("function: %d \n", global);
    return 0;
}

因此,答案是可以不手动初始化,但必需使用编译选项-fno-common

更多信息参见为什么全局变量一定要初始化?_aiduan6474的博客-CSDN博客

结构体对齐

结构体对齐__attribute__((__aligned__(a))),如果a为64,表示64字节对齐,即sizeof(结构体)=N*64。

0长数组

数组 vs 指针

c语言:char a[1];(常量) vs char *a;(变量)

warning: implicit declaration of function ‘isxdigit’

直接在.c文件中添加:

1
include <ctype.h>

或者:

1
extern int isxdigit(int c);

或者:

1
int isxdigit(int c);

声明vs定义

declaration - Declaring functions and variables multiple times in C++ - Stack Overflow

随机数

参见man srand中的EXAMPLE

assert

assert - cppreference.com

extern

代码格式化

  • clang-format

  • astyle

静态代码分析

  • tscancode

  • clang中的scan-build

单元测试

TODO

预处理

编译

clang

gcc

遇到过的问题

汇编

风格(AT&T vs intel)

gdb:

1
set disassembly-flavor att |intel

objdump:

1
objdump -M intel|att -d a.out >a.s

X86

8086

学习资源:

x86_i86(IA-32)

普林斯顿大学课程资源:

x86_64

在学习了前2者的前提下可自行摸索

ARM64

ARMv8

普林斯顿大学课程资源:

其他:

关于普林斯顿:

分析案例

怀疑是锁函数(arch_write_lock)导致的内存越界访问,修改了不该修改的内存。以下是该函数的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static inline void arch_write_lock(arch_rwlock_t *rw)
{
	unsigned int tmp;
 
	asm volatile(
	"	sevl\n"
	"1:	wfe\n"
	"2:	ldaxr	%w0, %1\n"
	"	cbnz	%w0, 1b\n"
	"	stxr	%w0, %w2, %1\n"
	"	cbnz	%w0, 2b\n"
	: "=&r" (tmp), "+Q" (rw->lock)
	: "r" (0x80000000)
	: "memory");
}

以及GDB反汇编结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Dump of assembler code for function arch_write_lock:
   0x0000000001bd036c <+0>:     sub     sp, sp, #0x20
   0x0000000001bd0370 <+4>:     str     x0, [sp, #8]
   0x0000000001bd0374 <+8>:     ldr     x2, [sp, #8]
   0x0000000001bd0378 <+12>:    mov     w1, #0x80000000                 // #-2147483648
   0x0000000001bd037c <+16>:    ldr     x0, [sp, #8]
   0x0000000001bd0380 <+20>:    sevl
   0x0000000001bd0384 <+24>:    wfe
   0x0000000001bd0388 <+28>:    ldaxr   w3, [x2]
   0x0000000001bd038c <+32>:    cbnz    w3, 0x1bd0384 <arch_write_lock+24>
   0x0000000001bd0390 <+36>:    stxr    w3, w1, [x2]
=> 0x0000000001bd0394 <+40>:    cbnz    w3, 0x1bd0388 <arch_write_lock+28>
   0x0000000001bd0398 <+44>:    nop
   0x0000000001bd039c <+48>:    mov     w0, w3
   0x0000000001bd03a0 <+52>:    str     w0, [sp, #28]
   0x0000000001bd03a4 <+56>:    nop
   0x0000000001bd03a8 <+60>:    add     sp, sp, #0x20
   0x0000000001bd03ac <+64>:    ret

通过查阅前述的ARMv8指令集文档及搜索某些复杂指令,最后再结合arm架构下spinlock原理 (代码解读)_adaptiver的专栏-CSDN博客_spinlock实现原理中以下部分的说明,终于理解了上述函数的功能:

image1.png

从而证明踩内存事件与该函数无关

遇到过的问题

GDB

使用技巧

输出格式控制

1
2
3
set print pretty on
set print elements 0
set disassembly-flavor att |intel

进程相关

1
set follow-fork-mode child

源文件相关

1
2
set substitute-path /foo/bar /mnt/cross
directory /fw_dat/ #详情参见How to point GDB to your sources | There is no magic here

调试相关

使用h <command>查看帮助

堆栈
  • f

  • bt -2 full

  • info frame

线程
  • thread apply
1
thread apply all bt
  • info threads

  • thread <id>

打印变量
  • ptype

  • p

  • x

  • inf locals

  • inf args

  • inf variables

修改变量
  • set var
1
(gdb) set var a=3 # int a;
1
2
	(gdb) set var $count = 0 
	(gdb) set var $foo = &{1.0}

set $list = (int *) malloc (10 * sizeof (int))

print {1,2,3,4}[2]

关于convenience variable的更多信息可参见Convenience Vars (Debugging with GDB)

流程控制
  • jump

  • return

  • finish

The return command does not resume execution; it leaves the program stopped in the state that would exist if the function had just returned. In contrast, the finish command (see section Continuing and stepping) resumes execution until the selected stack frame returns naturally.

——引用自Debugging with GDB - Returning

断点操作
  • commands

    • silent

    • end

  • b lineno if xxx

  • condition x [if xxx]

  • ignore

观察点
1
2
3
watch
rwatch
awatch

汇编相关

输出控制
1
2
3
4
5
6
7
8
9
10
h disassemble
disassemble
disassemble /m
layout split
objdump -S
x /12i $pc
display/3i $pc
undisplay xxx
set  disassemble-next-line on
show disassemble-next-line

gdb - View Both Assembly and C code - Stack Overflow

Show current assembly instruction in GDB - Stack Overflow

流程控制
1
2
ni #nexti
si #stepi
寄存器

Registers (Debugging with GDB)

自定义命令

常用命令:

1
2
3
show user
help user-defined
define abc

例子:

1
2
3
4
5
6
7
8
9
define adder
  set $i = 0
  set $sum = 0
  while $i < $argc
    eval "set $sum = $sum + $arg%d", $i
    set $i = $i + 1
  end
  print $sum
end

详情参见Define (Debugging with GDB)

保存断点

above GDB 7.2 (2011-08-23)

save breakpoints <filename>

Save all current breakpoint definitions to a file suitable for use

in a later debugging session. To read the saved breakpoint

definitions, use the `source’ command.

——引用自Save Breakpoints (Debugging with GDB)

below GDB 7.2 (2011-08-23)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.gdbinit:

define bsave
    shell rm -f brestore.txt
    set logging file brestore.txt
    set logging on
    info break
    set logging off
    # Reformat on-the-fly to a valid GDB command file
    shell perl -n -e 'print "break $1\n" if /^\d+.+?(\S+)$/g' brestore.txt > brestore.gdb
end
document bsave
  store actual breakpoints
end

define brestore
  source brestore.gdb
end
document brestore
  restore breakpoints saved by bsave
end

详情参见c++ - Getting GDB to save a list of breakpoints - Stack Overflow

使用案例

在N次循环时打断点

1
2
(gdb) set var $count = 0 
(gdb) break file.c:888 if ++$count == 1000

计算函数耗时

1
2
3
4
5
6
7
8
9
(gdb) set var $start=(struct timeval *) malloc(sizeof(struct timeval))
(gdb) set var $end =(struct timeval *) malloc(sizeof(struct timeval))
(gdb) b fun1
(gdb) c
(gdb) call gettimeofday($start,0)
(gdb) b +20
(gdb) c
(gdb) call gettimeofday($end,0)
(gdb) p ((end.tv_sec*1000*1000+end.tv_usec)-(start.tv_sec*1000*1000+start.tv_usec))

可以使用define封装成一个gdb command,可参考:

1
2
3
4
5
6
7
8
9
define timeme
set $last=clock()
n
set $timing=clock() - $last
if $timing>$arg0
printf "***long***\n"
end
printf "%d cycles, %f seconds\n", $timing, (float)$timing / 1000000
end

gdb break if called from

c++ - Is there any way to set a breakpoint in gdb that is conditional on the call stack? - Stack Overflow

遇到过的问题

推荐参考资料

构建系统

compile_commands.json

定义:JSON Compilation Database Format Specification — Clang 13 documentation

compile_commands.json是一个非常有用的文件,它长这样:

1
2
3
4
5
6
7
8
[
    {
        "command": "。。。",
        "directory": "。。。",
        "file": "。。。"
},
...
]

如何生成这个文件呢?对于cmake而言,只需传入一个额外的参数-DCMAKE_EXPORT_COMPILE_COMMANDS=ON即可

对于基于Makefile的构建系统,则需要安装辅助工具,例如bear:

安装方法如下(CentOS推荐从源码安装,且推荐编译旧版本,如2.1.5):

1
bear make

这样即可得到compile_commands.json

二进制分析

目标文件

.o文件

可执行文件

静态库

动态库

相关工具

binutils

使用如下命令即可获取该软件包中的所有命令:

1
rpm -ql binutils |grep bin/

下面只列举其中比较常用的

as

汇编命令。将汇编代码转换为目标文件(.o文件)

ar

打包命令。用于生成.a文件(静态库)

ld

链接命令。将多个.o文件链接成为一个.out文件(可执行文件)

nm

查看symbol的命令。用于查看elf文件的symbol

objdump

查看obj文件(.o, .a, .out, .so等)的信息

常用示例:

1
2
3
4
5
objdump -l -F -S -d a.out > a.s
objdump -M intel -d a.out >a.s
objdump -M amd-64 -d a.out >a.s
objdump -EB/EL -d a.out >a.s
objdump -I mcp/xxx sicuggio.bin
readelf

和objdump类似,只是更详细且不依赖于bfd库

strings

显示obj文件中的可打印字符

strip

删除obj文件中的标志

glibc-common

ldd

查看某个可执行文件依赖于哪些动态库。由于其可能执行该可执行文件,从而导致安全问题,所以man ldd中推荐使用objdump -p |grep NEEDED命令来代替

修订记录

修订时间 修订人 版本 说明
TODO wsxq2 1.0 初稿