动手调试C库-1 程序员的自我修养第11章笔记

本文介绍了如何从源码编译一个带有调试信息的RISC-V架构的musl C库,并通过GDB进行调试。作者选择了riscv-musl库,详细阐述了编译过程,包括配置选项和交叉编译。之后,通过一个简单的C程序展示了如何使用编译好的库,并利用gdb进行调试。文章最后提到了动态链接器的启动过程和_dlstart函数的相关源码。
摘要由CSDN通过智能技术生成

0. 序

在我的观感上来说吧,这11章写得真是灾难。灾难体现在两个地方,一个讲的实现机制比较古老了,比如.ctor段这些gcc早已经不再使用了。另一个是glibcwindowsMSVC混着讲,看着挺不舒服的(也许是因为我把前面讲windows的地方都跳过了)。

为了更实际地看看C库与C编译器是如何配合,完成C代码的初始化与运行的,尝试编译一个带调试信息的C库,手动调试是一个不错的选择。

1. 从源码编译C库

比较了几种C库的实现,最终选择了riscv-musl,选riscv主要是因为这个体系结构比较新,没有多少历史包袱,实现上可能会比较干净。下面的安装参考官网的docs

$ git clone https://github.com/riscvarchive/riscv-musl
$ cd riscv-musl
$ ./configure --prefix=/home/ckf/test/riscv-musl/build/ --syslibdir=/home/ckf/test/riscv-musl/build/lib --build=x86_64 --host=riscv64 --enable-debug CROSS_COMPILE=riscv64-linux-gnu-
# --prefix指定安装C库的基础目录,放在一个临时的目录里就好了
# --syslibdir指定动态链接器的位置
# --build 指定当前运行的平台
# --host 指定编译后的运行平台
# --target 指定该库的运行目标,比如说我们希望在X86环境下编译gcc,然后
# 让gcc运行在arm平台下,并且编译出risc-v的代码,那么就需要指定为
# --build=x86_64 --host=arm --target=riscv 这样,默认情况下target
# 等于host.
# 另外我们编译时保留调试信息,并且指定交叉编译的工具链。注意这里需要使用
# riscv-linux-gnu-, 之前试了一下riscv-unknown-elf-, 发现对应的ld
# 不支持动态链接.
$ make
$ make install

经过以上步骤,我们就得到了一个带调试信息的libc.so,可以在build/lib目录下检查C库是否有调试信息,出现了相应的.debug_*节就表示有调试信息。

$ readelf -S libc.so
···
  [17] .debug_aranges    PROGBITS         0000000000000000  0008a520
       0000000000011ac0  0000000000000000           0     0     16
  [18] .debug_info       PROGBITS         0000000000000000  0009bfe0
       0000000000103eb0  0000000000000000           0     0     1
  [19] .debug_abbrev     PROGBITS         0000000000000000  0019fe90
       000000000005ba2b  0000000000000000           0     0     1
  [20] .debug_line       PROGBITS         0000000000000000  001fb8bb
       0000000000073782  0000000000000000           0     0     1
  [21] .debug_frame      PROGBITS         0000000000000000  0026f040
       0000000000018c80  0000000000000000           0     0     8
  [22] .debug_str        PROGBITS         0000000000000000  00287cc0
       0000000000010ba9  0000000000000001  MS       0     0     1
  [23] .debug_loc        PROGBITS         0000000000000000  00298869
       00000000000bdf0d  0000000000000000           0     0     1
  [24] .debug_ranges     PROGBITS         0000000000000000  00356776
···

另外可以看到,lib目录下生成的动态链接器只是一个指向libc.so的符号链接,这说明在musl-Clibc.so本身就是动态链接器!

由于gcc的配置与C库是紧耦合的,如果我们直接编译时指定rpath让gcc链接到我们刚编译好的C库有可能会出错(因为即使C库换了,gcc的crt*.o文件还是用的原来的配置)。因此musl-C在安装目录下提供了一个对编译器的包装bin/musl-gcc

$ cat musl-gcc
#!/bin/sh
exec "${REALGCC:-riscv64-linux-gnu-gcc}" "$@" -specs "/home/ckf/test/riscv-musl/build/lib/musl-gcc.specs"
$ $ cat ../lib/musl-gcc.specs 
%rename cpp_options old_cpp_options

*cpp_options:
-nostdinc -isystem /home/ckf/test/riscv-musl/build/include -isystem include%s %(old_cpp_options)

*cc1:
%(cc1_cpu) -nostdinc -isystem /home/ckf/test/riscv-musl/build/include -isystem include%s

*link_libgcc:
-L/home/ckf/test/riscv-musl/build/lib -L .%s

*libgcc:
libgcc.a%s %:if-exists(libgcc_eh.a%s)

*startfile:
%{!shared: /home/ckf/test/riscv-musl/build/lib/Scrt1.o} /home/ckf/test/riscv-musl/build/lib/crti.o crtbeginS.o%s

*endfile:
crtendS.o%s /home/ckf/test/riscv-musl/build/lib/crtn.o

*link:
-dynamic-linker /home/ckf/test/riscv-musl/build/lib/ld-musl-riscv64.so.1 -nostdlib %{shared:-shared} %{static:-static} %{rdynamic:-export-dynamic}

*esp_link:


*esp_options:


*esp_cpp_options:

这里先不去细究上面是怎么回事的。

2. C库调试初体验

// havea.c
// musl-gcc -g -o havea havea.c
#include <stdio.h>
#include <unistd.h>

int a;
void init() __attribute__ ((constructor(540)));
void init(){
	  a = 7;
		write(1, "abcde", 5);
    printf("%s\n", __FUNCTION__);
}
int main(int argc, char* argv[]){
	printf("%d\n", a);
	//print_hello(argv[0]);
	//sleep(7200);
	return 0;
}

musl-gcc把上面的havea.c编译为可执行文件后,我们用qemu-riscv64gdb-multiarch进行调试。

$ cat .gdbinit 
set architecture riscv:rv64
target remote localhost:10000
layout split
# 主要省得每次都输入这些命令了

$ qemu-riscv64 -g 10000 havea
# 然后另外开启一个终端
$ gdb-multiarch
(gdb) file havea

这下应该就可以看到汇编代码了。预期首先看到的是动态链接器_dlstart函数,因为动态链接器首先拿到程序的控制权,_dlstart函数是用汇编写的,所以这一段没有源码。对应的源码目录是在ldso/dlstart.c

#include <stddef.h>
#include "dynlink.h"
#include "libc.h"

#ifndef START
#define START "_dlstart"
#endif

#define SHARED

#include "crt_arch.h"

#ifndef GETFUNCSYM
#define GETFUNCSYM(fp, sym, got) do { \
	hidden void sym(); \
	static void (*static_func_ptr)() = sym; \
	__asm__ __volatile__ ( "" : "+m"(static_func_ptr) : : "memory"); \
	*(fp) = static_func_ptr; } while(0)
#endif

hidden void _dlstart_c(size_t *sp, size_t *dynv)
{ 
// ...

上面的头文件crt_arch.h对应了_dlstart的源码,该文件位于arch/riscv64

__asm__(
".text\n"
".global " START "\n"
".type " START ",%function\n"
START ":\n"
".weak __global_pointer$\n"
".hidden __global_pointer$\n"
".option push\n"
".option norelax\n\t"
"lla gp, __global_pointer$\n"
".option pop\n\t"
"mv a0, sp\n"
".weak _DYNAMIC\n"
".hidden _DYNAMIC\n\t"
"lla a1, _DYNAMIC\n\t"
"andi sp, sp, -16\n\t"
"jal " START "_c"
);

一段si后,我们进入到_dlstart_c函数,这时候应该能看到源码了。
在这里插入图片描述
如果打上断点_start,便可以进入源程序的调试了。

OK,暂时写到这里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值