MacOS平台软件编译的若干问题(自定义Makefile 调试符号等)

MacOS平台软件编译的若干问题(自定义Makefile 调试符号等)

目前,MacOS/iOS的调试信息使用 dwarf 标准,最终的调试信息一般保存在 .dSYM bundle文件中,.dSYM bundle 像 .APP 文件一样,是个文件包,里面的子目录 .dSYM/Contents/Resources/DWARF/ 保存着 dwarf 调试符号(只包含调试符号信息的MachO文件,可以使用python工具 dwex 查看)。

注:以前MacOS中可以通过gcc -gstabs 选项使用旧的stabs nlist records调试信息格式,这种调试信息是内嵌到可执行文件中的,不过现在很少用了

调试时,调试器怎么找到对应的 .dSYM bundle ?

比较简单的方式是,只要将可执行文件和 .dSYM 放到同一个目录下,符合exenameexename.dSYM的范式,lldb就可以找到符号,或者使用 lldb 的 add-dsym命令加载指定位置的 .dSYM 符号。如果调试器找不到,它会做一个Spotlight Search,在spotlight-indexed的位置寻找。其实,还可以使用uuid来定位binary的dSYM,每当linker创建一个可执行文件时,它会在可执行文件的LC_UUID load command中生成一个128位的UUID,可以使用otool -hlv或者dwarfdump --uuid 来查看,这个UUID是唯一的,dsymutil创建 dSYM 时,它会包含这个UUID,调试器会检测 binary 和 dSYM 中的UUID是否一致。在crash report中包含一个Spotlight importer,可以通过命令mdfind "com_apple_xcode_dsym_uuids == E21A4165-29D5-35DC-D08D-368476F85EE1来查找,如果在Spotlight索引的位置可以找到dSYM。就可以正常加载符号

编译器怎么生成调试符号?

值得注意的是,为了提高link的速度,clang在编译源代码时,会把调试信息是保存在 .o object文件中(dwarf 调试信息一开始写入到 .s 文件中,然后 汇编器assembler 写入到 .o 文件中),然后将一个 debug map 写入到最终生成的可执行文件(即:可执行文件里面会通过绝对路径引用这些object文件,可以使用 dsymutil -dump-debug-map main 来查看,或者使用 nm -pa executable 来查看 debug map entries),让调试器可以找到对应的 object 文件以及里面的调试信息。调试信息不存在于最终生成的 Macho 文件中,即使使用 -g 开启调试信息,最终的MachO也不会包含太多调试信息,最多会保存着一些函数名(一旦strip就会被去掉)。正因为这种将可执行文件链接与调试信息链接分离的机制,显著提高了链接的速度,更多信息,可以参考这篇文章

http://wiki.dwarfstd.org/index.php?title=Apple%27s_%22Lazy%22_DWARF_Scheme

因为调试信息保存在 .o object 文件中,所以,如果你编译之后立即调试,根本不需要 .dSYM 文件,因为可以直接找到并利用 object 文件中的调试信息,这很好的满足了平常的开发时调试的场景的需求。一旦 object 文件被删除或者移动位置,调试信息就丢了,所以一般将 object 文件中的调试信息放到 .dSYM 文件包中,这个工作是由 dsymutil 这个工具完成的,它的功能相当于linux下的 objcopy 程序

dsymutil可以看作是一个 debug info linker, 做的事情就是读取MachO文件和它对应的object文件,获取调试信息,重定位,然后写入到 .dSYM中(的一个包含dwarf的binary中)

对于简单的程序,比如helloword,clang++ -o hello hello.cpp 会自动调用 dsymutil ,可以通过 clang++ 加上 -v 参数来查看调用 dsymutil 工具的过程

对于复杂的工程,一般会将编译 链接分开处理,所以需要我们手动调用 dsymutil 工具生成 .dSYM 文件,然后再使用 strip 将可执行文件的中调试信息删除

比如

$(target) : $(objects)
	@mkdir -p $(bin)
	$(CXX) $(objects) -o $(bin)/$@ $(LDFLAGS)
	
ifeq ($(os), Darwin)
	dsymutil $(bin)/$@
	$(STRIP) -u -r -arch all $(bin)/$@
else
	$(OBJCOPY) --only-keep-debug $(bin)/$(target) $(bin)/$(target).sym
	$(STRIP) --strip-debug --strip-unneeded $(bin)/$(target)
	$(OBJCOPY) --add-gnu-debuglink $(bin)/$(target).sym $(bin)/$(target)
endif
	

对于包含多个架构文件的Fat MachO文件

  • 如果能确保最终调用dsymutil时,Fat MachO中每一个架构的可执行文件对应的object都存在并且存在于原来的路径下,那么可以对Fat MachO直接调用dsymutil,dsymutil会处理fat MachO中每一个架构的可执行文件

  • 如果无法确保调用dsymutil时每一个架构的macho对应的object文件都存在,就只能在生成该架构的MachO时,马上调用 dsymutil,然后再编译下一个架构的代码,生成object文件后再调用 dsymuitl,这样,会生成多个不同架构的 dSYM 文件包,所以,最后使用lipo打包可执行文件的Fat MachO后,需要再用lipo打包不同架构的 dwarf MachO,生成一个支持多架构的 .dSYM 文件包

例如


all:
	echo "making arm version ..........."
	sleep 2
	rm -rf build_arm
	make -f makefile clean
	make -f makefile
	mkdir build_arm
	mv build/* build_arm/
	echo "making x86 version ..........."
	sleep 2
	rm -rf build_x86
	make -f makefile clean
	export ARCH=x86 
	ARCH=x86 make -f makefile
	mkdir build_x86
	mv build/* build_x86/
	echo "making universe binary........"
	sleep 2
	rm -rf build
	mkdir build
	@list='$(SUBDIRS)'; for binary in $$list; do \
		echo "making $$binary"; \
		lipo -create -output build/$$binary build_x86/$$binary build_arm/$$binary ; \
		dsymutil build/$$binary ; \
		rm build/$$binary.dSYM/Contents/Resources/DWARF/$$binary ; \
		lipo -create -output build/$$binary.dSYM/Contents/Resources/DWARF/$$binary build_x86/$$binary.dSYM/Contents/Resources/DWARF/$$binary build_arm/$$binary.dSYM/Contents/Resources/DWARF/$$binary ; \
	done;

  • 编译时,优化相关的开关 -O0 -O2 等,并不影响调试信息的生成,虽然更高的优化等级生成的代码比-O0更难以调试
refer:
  1. https://stackoverflow.com/questions/10044697/where-how-does-apples-gcc-store-dwarf-inside-an-executable/12827463#12827463
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

quentin_d

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值