elf source path
readelf
holds the key, but you don't want to dump strings willy-nilly. Use the -wi
option to properly format the info.
For example, create a shell script thus:
readelf -wi $1 | grep -B1 DW_AT_comp_dir |
awk '/DW_AT_name/{name = $NF; getline; print $NF"/"name}'
which further summarizes the output of readelf -wi
.
I suggest to use this command and tools like sed or grep.
$ readelf --string-dump=.debug_str YOUR_PROGRAM
this show path and source file name:
$ readelf --string-dump=.debug_str YOUR_PROGRAM | sed -n '//|.c/{s/.*] //p}'
https://blog.csdn.net/qq_42800075/article/details/105023699
https://blog.csdn.net/u013490896/article/details/79323783
https://biscuitos.github.io/blog/Linux-0.11-Usermanual/
https://github.com/Wangzhike/HIT-Linux-0.11/blob/master/1-boot/OS-booting.md
https://my.oschina.net/lngligelang/blog/221411
https://my.oschina.net/lngligelang/blog/221771
https://my.oschina.net/lngligelang/blog/223200
BiscuitOS/output/linux-0.11/linux/linux/arch/x86/boot/Makefile
bootsect: bootsect.o
$(Q)$(LD) $(LDFLAGS) $(local_LDFLAGS) -o $@ $<
http://bbs.chinaunix.net/thread-4262483-1-1.html
调试 BiscuitOS 内核
BiscuitOS 支持 GDB 调试,开发可以使用 GDB 调试 BiscuitOS 的所有代码。
BiscuitOS 的调试分为两部分,根据 Intel X86 的使用手册,系统上电之后,会将磁盘的第一个扇区加载到内存的 0x7C00 的位置进行运行。
此时, 系统处于实时模式,实时模式下,寻址的长度 16 位。 实时模式下,系统为内核的加载做准备,但准备好条件之后,
系统将进入保护模式,保护模式运行在 32 bit 上,因此,两部分由于指令长度不同, GDB 调试的情况不同。
因此,本节将介绍两种模式下,如何使用 GDB 调试内核。
调试内核源码之前,请先准备好如下工具:
gdb, objdump
请确保源码树中顶层的 Makefile 中, DEBUG 选项已经打开
- vi */BiscuitOS/Makefile
- --- DEBUG :=
- +++ DEBUG := 1
1. 实时模式下内核调试
系统在上电之后,会将启动磁盘的第一个扇区加载到内存 0x7c00 处开始运行, 所以开发者可以使用 gdb,在地址 0x7c00 处设置一个断点,
然后从这个断点处开始调试。 具体步骤如下:
1) 在 PC 机上准备两个终端,第一个终端作为 DEBUG HOST 使用,如下
- cd */BiscuitOS/
- make
- make debug
运行上面的命令之后,该终端会进入等待状态,等待 DEBUG TARGET 的相应,如下图
<ignore_js_op>
2) 在另一个终端作为 DEBUG TARGET 使用,如下:
- cd */BiscuitOS/tools/build
- gdb .debug_bootsect
执行上面的命令之后,终端会进入 GDB 的 debug 模式, 在 debug 模式下,使用如下命令:
- (gdb) target remote :1234
- (gdb) b *0x7C00
- (gdb) c
- (gdb) d
- Delete all breakpoints? (y or n) y
- (gdb) ni
执行完上面的命令之后,终端就进入 debug 模式,你可以使用 gdb 命令对代码进行调试,如下图, 使用 “info reg” 打印寄存器的信息
<ignore_js_op>
在实时模式进行单步调试应该使用 ni 命令,跳转到函数内部,应该使用 si
3) 实时模式下如何调试中断
在实时模式下,如果使用单步进行调试,如果遇到 int 中断,估计单步一直下去,永远跳不出这个坑,那么如何调试中断呢?我这里举个例子
例如在 */BiscuitOS/boot/bootsect.s 中有一段关于中断的代码片段:
- mov $0x0000, %dx # head 0
- mov $DEVICE_NR, %dl # dirve 0
- mov $0x0002, %cx # sector 2, track 0
- mov $0x0200, %bx # address = 512, in INITSEG
- .equ AX, 0x200+SETUPLEN
- mov $AX, %ax # service 2, nr of sectors
- int $0x13 # read it
- mov %ax, %ax
在上面的代码中,内核会通过调用 0x13 中断去读扇区的数据,判断一个中断是否成功,我们必须通过 EFLAGS 寄存器进行判断,
但是如果使用单步进行调试的话,单步执行到 “int $0x13” 之后,一直无法跳出中断,为此我们可以使用如下的方法进行调试。
> 首先在 “int $0x13” 之后添加一条汇编指令 “mov %ax, %ax”, 这条汇编指令没有任何实际意义,只是用于调试。
添加完之后,重新编译系统
- cd */BiscuitOS
- make clean
- make
- make debug
> 此时使用第三个终端,在终端中执行如下命令:
- cd */BiscuitOS/tools/build
- objdump -x -s -S -dh .debug_bootsect
此时会将 .debug_bootsect 反汇编出来, .debug_bootsect 是 */BiscuitOS/boot/bootsect.s 的链接文件,
然后我们在导出的信息中找到上面代码对应的反汇编代码,如下:
- 28: ba 00 00 b2 00 mov $0xb20000,%edx
- mov $DEVICE_NR, %dl # dirve 0
- mov $0x0002, %cx # sector 2, track 0
- 2d: b9 02 00 bb 00 mov $0xbb0002,%ecx
- mov $0x0200, %bx # address = 512, in INITSEG
- 32: 02 b8 04 02 cd 13 add 0x13cd0204(%eax),%bh
- .equ AX, 0x200+SETUPLEN
- mov $AX, %ax # service 2, nr of sectors
- int $0x13 # read it
- mov %ax, %ax
- 38: 89 c0 mov %eax,%eax
- jnc ok_load_setup # ok -continue
通过上面的信息,可知,我们之前添加的 “mov %ax, %ax” 对应的链接地址是 0x38
由于下面的步骤需要对链接器和加载器有一定的认识,才知道为什么这么做,这里我不做过多的解释,大家参照我的方法做
接着,通过系统原来我们知道,系统启动的过程中,将启动磁盘的第一个扇区加载到内存 0x7c00 处,
根据 Linux 系统的实现逻辑,代码会将代码段加载到内存地址 0x90000 处,此时 CS 寄存器的值也是 0x9000,
因此,我们在打断点时候,段地的地址应该是: 0x90000 + 0x38 = 0x90038.
此时在第二个终端中输入如下命令:
- cd */BiscuitOS/tools/build/
- gdb .debug_bootsect
- (gdb) target remote :1234
- (gdb) b *0x90038
- (gdb) c
- (gdb) d
- Delete all breakpoints? (y or n) y
- (gdb) info reg
此时会将系统寄存器的值都答应出来,我们重点查看 eflags 的值,通过它的值来确定中断的执行是成功或失败。
2. 保护模式下的调试
系统在实时模式下初始化完毕之后,就要开始加载内核,保护模式下, gdb 的调试和实时模式下有不同之处,至于原因
可以归咎与 GDT 段描述符表的机制,此时段寄存器不像实时模式下简单的做地址偏移,保护模式下,段寄存器做为段选择子,
以此在 GDT 或 LDT 选择描述符来进行代码跳转,因此,调试方法可以按如下步骤,(该调试时候除 boot/bootsect.s 和 boot/setup.s 之外的所有代码)
1) 一个终端中作为 DEBUG HOST,输入如下命令
- cd */BiscuitOS
- make
- make debug
2) 另外一个终端作为 DEBUG TARGET, 输入如下命令
- cd */BiscuitOS/tools/build
- gdb system
- (gdb) target remote :1234
- (gdb) b main
- (gdb) c
- (gdb) n
- (gdb) s
在保护模式下,可以使用 n 进行单步调试,进入函数内部可以使用 s
更多 gdb 调试方法可以参考 gdb 的 User Manual
3) 调试保护模式下的汇编代码
我们可以采用和实时模式下的套路,使用 objdump 工具进行反汇编分析, 如下面的例子
内核加载后执行的第一条命令是从物理地址 0 开始的,汇编代码如下:
- pg_dir:
- .globl startup_32
- startup_32:
- movl $0x10, %eax # 0x10, Global data segment.
- mov %ax, %ds
- mov %ax, %es
- mov %ax, %fs
- mov %ax, %gs
我们使用 objdump 工具,如下:
- cd */BiscuitOS/tools/build
- objdump -x -s -S -dh system
从中我们可以获得:
- 00000000 <pg_dir>:
- pg_dir:
- .globl startup_32
- startup_32:
- movl $0x10, %eax # 0x10, Global data segment.
- 0: b8 10 00 00 00 mov $0x10,%eax
- mov %ax, %ds
- 5: 8e d8 mov %eax,%ds
- mov %ax, %es
- 7: 8e c0 mov %eax,%es
- mov %ax, %fs
- 9: 8e e0 mov %eax,%fs
由于保护模式下第一行代码是从物理地址开始,所以 startup_32 的断点地址为: 0x0 + 0,
在 DEBUG TARGET 终端中输入如下命令:
- cd */BiscuitOS/tools/build
- gdb system
- (gdb) target remote :1234
- (gdb) b *0x0
- (gdb) c
- (gdb) info reg
执行上面的代码,我们就可以单步调试保护模式的汇编代码。
http://news.eeworld.com.cn/mcu/article_2016040825595.html
我们写一个汇编程序,控制GPIO,从而控制外接的LED,代码如下;
.text
.global _start
_start:
LDR R0,=0x56000010 @GPBCON寄存器
MOV R1,# 0x00000400
str R1,[R0]
LDR R0,=0x56000014
MOV R1,#0x00000000
STR R1,[R0]
MAIN_LOOP:
B MAIN_LOOP
代码很简单,就是一个对io口进行设置然后写数据。我们看它是如何编译的,注意我们这里使用的不是arm-linux-gcc而是arm-elf-gcc,二者之间没有什么比较大的区别,arm-linux-gcc可能包含更多的库文件,在命令行的编译上面是没有区别。我们来看是如何编译的:
arm-elf-gcc -g -c -o led_On.o led_On.s 首先纯编译不连接
arm-elf-ld -Ttext 0x00000000 -g led_On.o -o led_on_elf
用Ttext指明我们程序存储的地方,这里生成的是elf文件,还不是我们真正的bin,但是可以借助一些工具可以进行调试。