-
-
[原创]记一次曲折的多资源文件拆分折腾过程(3)—— 在 windows 下用 linux 神器 gdb 调试 git
-
发表于: 2021-11-8 08:08 10223
-
在前面两篇文章 记一次曲折的多资源文件拆分折腾过程(1) 和 记一次曲折的多资源文件拆分折腾过程(2) 中,已经把折腾过程中遇到的问题都弄清楚了。因为对这个问题印象太深刻了,而且 git
又是开源的,于是特地翻看了 git
的源码,并且在 windows
上用 gdb
简单调试了一波。
说明: 本篇文章适合 geek
阅读,全是一些源码查看及编译环境+调试的总结。
<!--more-->
在 github
上找到 git for windows
的仓库,https://github.com/git-for-windows/git,使用如下命令克隆下来即可。
当然,也可以直接下载压缩包。在 git
仓库的主页的右上侧有一个名为 Code
的按钮,点击后,会弹出一个悬浮框,里面有几个选项,其中一个是 Download ZIP
按钮,点击此按钮即可下载压缩好的代码。
说明:
git-for-windows/git
fork
自 git/git
,也可以从 https://github.com/git/git 下载。
如果仅仅想查看代码的话,可以通过此方式下载。如果想调试 git
的话,不必在这里手动下载,可以通过后面介绍的 sdk init git
进行下载。
主要有两点疑惑需要解答。
本以为很简单就能找到关键代码,但是代码复杂度远超过最初的预想。不过好在整个代码组织结构还是非常清楚规范的 —— 基本上每个子命令都有一个对应的 .c
文件,比如 add
对应的文件是 builtin\add.c
。对应的功能入口函数名以 cmd_
开头,加上命令名,比如 add
命令对应的入口函数是 cmd_add
。
查看 git add
对应的代码执行流程,确认在哪里做的检查,并报的警告。
git add
命令对应的入口函数是 builtin/add.c
中的 cmd_add
。
刚开始查看的时候,是从 cmd_add
看起的,因为这个是 git add
对应的入口函数。由于对 git
源码不熟悉,花费了很长时间,于是转换思路,从报错信息往回找。搜索提示中的关键字 contains a byte order mark (BOM).
,很快就搜到了关键函数——convert.c
中的 static int validate_encoding(const char *path, const char *enc, const char *data, size_t len, int die_on_error)
从代码上看,是进入到了 has_prohibited_utf_bom(enc, data, len)
对应的分支中。按住 ctrl
,用鼠标点击对应的函数,就跳转到了对应函数的实现。对应的实现在 utf8.c
中。摘录如下:
大概意思是,如果指定了 UTF-16BE
或者 UTF-16LE
(另外一个分支是 UTF-32BE
UTF-32LE
)并且在文件中检测到了对应的 BOM
头,就报错。对应的 BOM
头定义如下:
.rc
文件中确实有 utf16_be_bom
。
看来,如果 working-tree-encoding
指定为 UTF-16LE
或 UTF-16BE
,那么文件中不能包含 BOM
头。也就是说 UTF-16LE/BE
对应的文件应该是不包含 BOM
头的。
值得注意的是,validate_encoding()
函数中还做了另外一个判断,也就是 is_missing_required_utf_bom()
。如果 working-tree-encoding
指定为 UTF-16
, 那么必须指定 UTF-16LE
或者 UTF-16BE
。
找到了最关键的函数,向上找一找谁调用了这个函数,可以在 validate_encoding()
上,右键,选择 Lookup References...
就可以查看所有引用此函数的地方了,后面会多次用到这个方法,这里放一张动态图,后面就不赘述了。
说明:如果已经有了搜索结果,Source Insight
会提示追加还是覆盖。我一般选的是追加。
因为调用链比较长,我挑选了一条可能的调用链,如下:
整个过程是使用 Lookup References...
从下向上查找的。
至此,git add .
对应的调用栈就整理出来了。接下来,需要确定的是:在执行 git checkout .
时,是否会像文档中描述的那样执行编码转换工作?
查看 git checkout .
的执行流程,确认是否有编码转换相关调用。
与 add
一样,checkout
命令对应的入口函数也在 builtin
目录下,对应的文件名是 checkout.c
,对应的入口函数是 cmd_checkout
。git restore
和 git switch
对应的入口函数的实现也在这个文件中,而且都调用了 checkout_main
。
查找调用函数的过程中,发现在 entry.c
中的 write_entry()
函数中有如下代码片段
根据注释可以猜测,这个是把内部存储格式转换成工作目录所需的格式。
最后一个函数是 reencode_string_len()
,对应的实现如下:
可以很明显的发现,reencode_string_len()
在处理 out_encoding
是 UTF-16
的时候,直接按大端进行转换。这就是为什么 .rc
文件的 working_tree_encoding
设置为 UTF-16
时,执行 git checkout .
后,文件编码变成 UTF-16BE BOM
的原因了。
手动整理的调用链如下:
代码看完了,准确的说应该是猜完了。但是 git
在实际运行的时候是按照上面的流程执行的吗?万一猜错了呢?为了进一步落实打破沙锅问到底的精神,而且作为一个调试爱好者,不通过调试手段确认一下,总觉得是不踏实。而且有源码,不调试一波,实在是浪费。于是,花了半天左右的时间,根据官方文档的提示,趟出了一条调试 git
的路,验证了自己的猜想,踏实!这下能睡个安稳觉了。
windows
并没有自带 gdb
调试工具。安装好 git sdk
后,可以在安装目录下找到 gdb.exe
。能在 windows
下用 linux
调试神器,真香!
从 网上下载的 git
应该是 release
版,不带调试符号。如果直接调试设置断点的话,gdb
会报错,提示找不到符号表。
整个环境搭建过程参照 https://github.com/git-for-windows/git/wiki/Technical-overview 和 https://github.com/git-for-windows/git/wiki/Debugging-Git 进行。
我把整个过程整理如下:
先到下载页面下载 git sdk
,我下载的是 64 位 的 git-sdk-installer-1.0.8-64.7z.exe
。
下载好 git sdk
安装包后,以管理员权限运行。
说明:在执行 sdk init git
之前,目录 C:\git-sdk-64\usr\src\git\
里面没有源码,执行完 sdk init git
,这个目录下包含了 git
的源码文件(带完整历史记录)。如下图:
修改 C:\git-sdk-64\usr\src\git\config.mak
文件的文件内容为如下形式:
默认下载完之后的内容如下,如果在后续步骤执行 cd usr/src/git && make install
的时候什么也没干就结束了的话,可以删除第二行,再试。
双击 C:\git-sdk-64\
下的 git-bash.exe
,然后执行 cd usr/src/git
切换到 git
目录。
注意: 输入的是 linux
中的路径分隔符 /
而不是 windows
中的路径分隔符 \
。
执行 make install
,就可以开始编译了。
编译结束后,如果一切正常就生成了带符号的 git.exe
。
说明:
虽然生成的是 .exe
文件,但是并不会同时生成一个 git.pdb
文件。
生成的文件会在 C:\git-sdk-64\usr\src\git
下,但是并不能直接执行此目录下的文件,如果直接执行会报错。
使用 gdb
调试 git
。大体用法如下:
至此,调试环境已经完全搭建好了,可以开心的调试了。
首先,查看 git add .
的调用过程,明确关键检查逻辑。
在调试之前,先建立一个简单的测试环境。建立一个 UTF-16LE BOM
编码的文件 utf-l6le-bom.txt
,并编辑其内容为 test
。设置 .gitattributes
的 working-tree-encoding
为 UTF-16LE
,然后执行 git init
初始化仓库,然后就可以使用 gdb
调试 git add .
了。
在打开的 shell
终端中输入如下命令 C:/git-sdk-64/mingw64/bin/gdb.exe --args C:/git-sdk-64/mingw64/bin/git.exe add .
。如果成功,会像下图这样。
接着为函数 validate_encoding
设置断点,在 gdb
中输入 b validate_encoding
(b
是 break
的缩写),提示设置成功后,输入 r
(run
)使程序重新运行起来。这时候程序应该中断下来了。
从上图可知,path
的值是 utf16-le-bom.txt
,是需要 add
的文件,enc
的值是 UTF16-LE
,是 working-tree-encoding
中指定的值。data
应该指向了 utf16-le-bom.txt
中的内容,len
指的是 data
的长度。查看一下 data
的具体内容。
在 gdb
中可以使用 x
命令可以查看内存中的数据(如果对 gdb
命令不不熟悉,可以输入 help cmd
,即可查看对应命令的帮助了),输入 x/10bx data
即可查看从 data
开始的 10
个字节的内容了。
可以看到,文件内容是 UTF-16LE BOM
存储的,与磁盘上的文件内容一致。最后,看一下调用栈与之前查看源码时猜测的调用栈是否一样。输入 bt
(backtrace
)即可查看调用栈。
中间一些执行不太一样,不过也差不多,通过源码猜的调用栈应该是在其它情况下运行的。至此,执行 git add
时所做的编码格式检查就检查完了。
接下来,要明确的是执行 git checkout .
的时候是否会调用到编码转换的函数中去,具体传递的参数是什么?
首先,建立一个 UTF-16LE BOM
编码的文件 utf-l6le-bom.txt
,并编辑其内容为 test
。设置 .gitattributes
的 working-tree-encoding
为 UTF-16
,然后执行 git init && git add . && git commit -m "init"
。
然后修改 utf-16le-bom.txt
的内容为 test1
,然后就可以使用 gdb
调试 git checkout .
命令了。
在打开的 shell
终端中输入如下命令
这里就不贴图了,具体参考上面调试 git add
命令时的贴图。
接着为函数 reencode_string_len
设置断点,在 gdb
中输入 b reencode_string_len
,提示设置成功后,输入 r
使程序重新运行起来。这时候程序应该中断下来了。
从图中高亮部分可知,关键参数和我们想的一样。in
是 test
,也就是第一次提交的内容。insz
是 4
,表示传入字符串的长度。out_encoding
是 UTF-16
(working-tree-encoding
中指定的编码,写文件到磁盘上的编码)。in_encoding
是 UTF-8
( git
内部存储数据时使用的编码)。outsz
保存了传出字符串的长度。结果字符串通过返回值返回。
输入 bt
查看调用栈,如下:
跟之前通过查看源码猜测的完全一样!(我这里只高亮了函数名,在每帧的结尾处,gdb
为我们列出了该函数所在的文件及对应的行数)。
输入 fi
(finish
) 结束当前函数的运行,观察返回值(返回值保存了转码后的字符串)。有汇编基础的小伙伴儿都知道,一般返回值是放在 rax
中的。输入 x/10x $rax
即可按十六进制显示从 $rax
开始的 10
个字节了,如下图:
可以看到,前两个字节是大端 BOM
头(0xFE 0xFF
,小端 BOM
头是 0xFF 0xFE
)。执行完命令后,使用十六进制查看器观察到的文件内容与调试时看到的一样,说明把编码转换后的内容写入到了磁盘中。checkout
后,磁盘文件内容下图所示:
如果在调试过程中想查看相关源码,可以输入 l
(list
) 进行查看。下图是执行 f 0
(frame 0
) 切换到 0
号栈帧后,再通过 l
命令查看源码的截图。
至此,这两个疑惑都得到了圆满的解答!
https://github.com/git-for-windows/git/wiki/Technical-overview
https://github.com/git-for-windows/git/wiki/Debugging-Git
https://blog.csdn.net/qq_28351609/article/details/114855630
# 说明:可能需要科学上网才能正常克隆
# 如果只想获得最新的提交可以使用下面的命令
git clone
-
-
depth
1
https:
/
/
github.com
/
git
-
for
-
windows
/
git.git
# 如果想克隆所有提交集,执行下面的命令
git clone https:
/
/
github.com
/
git
-
for
-
windows
/
git.git
# 说明:可能需要科学上网才能正常克隆
# 如果只想获得最新的提交可以使用下面的命令
git clone
-
-
depth
1
https:
/
/
github.com
/
git
-
for
-
windows
/
git.git
# 如果想克隆所有提交集,执行下面的命令
git clone https:
/
/
github.com
/
git
-
for
-
windows
/
git.git
static const char utf16_be_bom[]
=
{
'\xFE'
,
'\xFF'
};
static const char utf16_le_bom[]
=
{
'\xFF'
,
'\xFE'
};
static const char utf32_be_bom[]
=
{
'\0'
,
'\0'
,
'\xFE'
,
'\xFF'
};
static const char utf32_le_bom[]
=
{
'\xFF'
,
'\xFE'
,
'\0'
,
'\0'
};
static const char utf16_be_bom[]
=
{
'\xFE'
,
'\xFF'
};
static const char utf16_le_bom[]
=
{
'\xFF'
,
'\xFE'
};
static const char utf32_be_bom[]
=
{
'\0'
,
'\0'
,
'\xFE'
,
'\xFF'
};
static const char utf32_le_bom[]
=
{
'\xFF'
,
'\xFE'
,
'\0'
,
'\0'
};
cmd_add()
/
/
builtin
/
add.c
-
> add_files()
/
/
builtin
/
add.c
-
> add_file_to_index()
/
/
read
-
cache.c
-
> add_to_index()
/
/
read
-
cache.c
-
> index_path()
/
/
object
-
file
.c
-
> index_fd()
/
/
object
-
file
.c
-
> index_stream_convert_blob()
/
/
object
-
file
.c
-
> convert_to_git_filter_fd()
/
/
convert.c
-
> encode_to_git()
/
/
convert.c
-
> validate_encoding()
/
/
convert.c
cmd_add()
/
/
builtin
/
add.c
-
> add_files()
/
/
builtin
/
add.c
-
> add_file_to_index()
/
/
read
-
cache.c
-
> add_to_index()
/
/
read
-
cache.c
-
> index_path()
/
/
object
-
file
.c
-
> index_fd()
/
/
object
-
file
.c
-
> index_stream_convert_blob()
/
/
object
-
file
.c
-
> convert_to_git_filter_fd()
/
/
convert.c
-
> encode_to_git()
/
/
convert.c
-
> validate_encoding()
/
/
convert.c
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课