• 记一次曲折的多资源文件拆分折腾过程(3)在 windows 下用 linux 神器 gdb 调试 git


    记一次曲折的多资源文件拆分折腾过程(3)在 windows 下用 linux 神器 gdb 调试 git

    前言

    在前面两篇文章 记一次曲折的多资源文件拆分折腾过程(1)记一次曲折的多资源文件拆分折腾过程(2) 中,已经把折腾过程中遇到的问题都弄清楚了。因为对这个问题印象太深刻了,而且 git 又是开源的,于是特地翻看了 git 的源码,并且在 windows 上用 gdb 简单调试了一波。

    说明: 本篇文章适合 geek 阅读,全是一些源码查看及编译环境+调试的总结。

    git 源码下载

    github 上找到 git for windows 的仓库,https://github.com/git-for-windows/git,使用如下命令克隆下来即可。

    # 说明:可能需要kexue上网才能正常克隆
    
    # 如果只想获得最新的提交可以使用下面的命令
    git clone --depth 1 https://github.com/git-for-windows/git.git
    
    # 如果想克隆所有提交集,执行下面的命令
    git clone https://github.com/git-for-windows/git.git

    当然,也可以直接下载压缩包。在 git 仓库的主页的右上侧有一个名为 Code 的按钮,点击后,会弹出一个悬浮框,里面有几个选项,其中一个是 Download ZIP 按钮,点击此按钮即可下载压缩好的代码。

    download-git-zip-from-github
    download-git-zip-from-github

    说明:

    git-for-windows/git forkgit/git ,也可以从 https://github.com/git/git 下载。

    如果仅仅想查看代码的话,可以通过此方式下载。如果想调试 git 的话,不必在这里手动下载,可以通过后面介绍的 sdk init git 进行下载。

    明确目标

    主要有两点疑惑需要解答。

    1. 为什么在 .gitattributes 中把 .rcworking-tree-mode 设置成 UTF-16LE 后,在执行 git add 的时候会报错,提示设置成 UTF-16
      git-add-warning-and-fail
    2. 执行 git checkout . 的时候,是否会把 utf8 编码的内容转换成 working-tree-encoding 中指定的编码格式吗?

    查看代码

    本以为很简单就能找到关键代码,但是代码复杂度远超过最初的预想。不过好在整个代码组织结构还是非常清楚规范的 —— 基本上每个子命令都有一个对应的 .c 文件,比如 add 对应的文件是 builtin\add.c。对应的功能入口函数名以 cmd_ 开头,加上命令名,比如 add 命令对应的入口函数是 cmd_add

    1. 查看 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)

      validate_encoding

      从代码上看,是进入到了 has_prohibited_utf_bom(enc, data, len) 对应的分支中。按住 ctrl,用鼠标点击对应的函数,就跳转到了对应函数的实现。对应的实现在 utf8.c 中。摘录如下:

      has_prohibited_utf_bom

      大概意思是,如果指定了 UTF-16BE 或者 UTF-16LE (另外一个分支是 UTF-32BE UTF-32LE)并且在文件中检测到了对应的 BOM 头,就报错。对应的 BOM 头定义如下:

      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'};

      .rc 文件中确实有 utf16_be_bom

      show-rc-bom-header

      看来,如果 working-tree-encoding 指定为 UTF-16LEUTF-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... 就可以查看所有引用此函数的地方了,后面会多次用到这个方法,这里放一张动态图,后面就不赘述了。

      lookup-references

      说明:如果已经有了搜索结果,Source Insight 会提示追加还是覆盖。我一般选的是追加。

      因为调用链比较长,我挑选了一条可能的调用链,如下:

      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

      整个过程是使用 Lookup References... 从下向上查找的。

      至此,git add . 对应的调用栈就整理出来了。接下来,需要确定的是:在执行 git checkout . 时,是否会像文档中描述的那样执行编码转换工作?

    2. 查看 git checkout . 的执行流程,确认是否有编码转换相关调用。

      add 一样,checkout 命令对应的入口函数也在 builtin目录下,对应的文件名是 checkout.c,对应的入口函数是 cmd_checkoutgit restoregit switch 对应的入口函数的实现也在这个文件中,而且都调用了 checkout_main

      查找调用函数的过程中,发现在 entry.c 中的 write_entry() 函数中有如下代码片段

      /*
      * Convert from git internal format to working tree format
      */
      if (dco && dco->state != CE_NO_DELAY) {
        ret = async_convert_to_working_tree_ca(ca, ce->name, new_blob, size, &buf, &meta, dco);
        if (ret && string_list_has_string(&dco->paths, ce->name)) {
          free(new_blob);
          goto delayed;
        }
      } else {
        ret = convert_to_working_tree_ca(ca, ce->name, new_blob, size, &buf, &meta);
      }

      根据注释可以猜测,这个是把内部存储格式转换成工作目录所需的格式。

      最后一个函数是 reencode_string_len(),对应的实现如下:

      reencode_string_len

      可以很明显的发现,reencode_string_len() 在处理 out_encodingUTF-16 的时候,直接按大端进行转换。这就是为什么 .rc 文件的 working_tree_encoding 设置为 UTF-16 时,执行 git checkout . 后,文件编码变成 UTF-16BE BOM 的原因了。

      手动整理的调用链如下:

      cmd_checkout() // builtin/checkout.c
      -> checkout_main() // builtin/checkout.c
       -> checkout_paths() // builtin/checkout.c
        -> checkout_worktree() // builtin/checkout.c
         -> checkout_entry() // builtin/checkout.c
          -> checkout_entry_ca() // builtin/checkout.c
           -> write_entry() // entry.c
            -> convert_to_working_tree_ca() // entry.c
             -> convert_to_working_tree_ca_internal() // entry.c
              -> encode_to_worktree() // convert.c
               -> reencode_string_len() // utf8.c

    代码看完了,准确的说应该是猜完了。但是 git 在实际运行的时候是按照上面的流程执行的吗?万一猜错了呢?为了进一步落实打破沙锅问到底的精神,而且作为一个调试爱好者,不通过调试手段确认一下,总觉得是不踏实。而且有源码,不调试一波,实在是浪费。于是,花了半天左右的时间,根据官方文档的提示,趟出了一条调试 git 的路,验证了自己的猜想,踏实!这下能睡个安稳觉了。

    调试环境搭建 & 编译

    1. windows并没有自带 gdb 调试工具。安装好 git sdk 后,可以在安装目录下找到 gdb.exe。能在 windows下用 linux 调试神器,真香!

      search-gdb-in-disk

    2. 网上下载的 git 应该是 release 版,不带调试符号。如果直接调试设置断点的话,gdb 会报错,提示找不到符号表。

      no-symbol-table-load

    整个环境搭建过程参照 https://github.com/git-for-windows/git/wiki/Technical-overviewhttps://github.com/git-for-windows/git/wiki/Debugging-Git 进行。

    我把整个过程整理如下:

    1. 先到下载页面下载 git sdk,我下载的是 64 位git-sdk-installer-1.0.8-64.7z.exe

    2. 下载好 git sdk 安装包后,以管理员权限运行。

    3. 运行成功后,执行 sdk init git(执行此命令可以帮我们下载 git 源码,就可以省去手动下载源码的步骤了)。

    install-and-init-sdk
    install-and-init-sdk

    **说明:**在执行 sdk init git 之前,目录 C:\git-sdk-64\usr\src\git\ 里面没有源码,执行完 sdk init git ,这个目录下包含了 git 的源码文件(带完整历史记录)。如下图:successful-installed-src-folder

    1. 修改 C:\git-sdk-64\usr\src\git\config.mak 文件的文件内容为如下形式:

      DEVELOPER=1
      ifndef NDEBUG
      CFLAGS := $(filter-out -O2,$(CFLAGS))
      ASLR_OPTION := -Wl,--dynamicbase
      BASIC_LDFLAGS := $(filter-out $(ASLR_OPTION),$(BASIC_LDFLAGS))
      endif

      默认下载完之后的内容如下,如果在后续步骤执行 cd usr/src/git && make install 的时候什么也没干就结束了的话,可以删除第二行,再试。

      DEVELOPER=1
      SKIP_DASHED_BUILT_INS=YesPlease
      ifndef NDEBUG
      CFLAGS := $(filter-out -O2,$(CFLAGS))
      ASLR_OPTION := -Wl,--dynamicbase
      BASIC_LDFLAGS := $(filter-out $(ASLR_OPTION),$(BASIC_LDFLAGS))
      endif
    2. 双击 C:\git-sdk-64\ 下的 git-bash.exe,然后执行 cd usr/src/git 切换到 git 目录。

      注意: 输入的是 linux 中的路径分隔符 / 而不是 windows 中的路径分隔符 \

    3. 执行 make install,就可以开始编译了。

      open-sdk-cd-to-usr-src-git-and-make-install

    4. 编译结束后,如果一切正常就生成了带符号的 git.exe

      build-success

      说明:

      1. 虽然生成的是 .exe 文件,但是并不会同时生成一个 git.pdb 文件。

      2. 生成的文件会在 C:\git-sdk-64\usr\src\git 下,但是并不能直接执行此目录下的文件,如果直接执行会报错。

        start-git-under-usr-src-git-failed

        1. 生成的 git.exe 及几个其它 .exe 文件会被自动拷贝到 C:\git-sdk-64\mingw64\bin\ 目录下,还会拷贝一些其它文件到对应目录下。可以通过 git status 查看。

        newly-built-exe-with-debug-info

    5. 使用 gdb 调试 git。大体用法如下:

      # debug git checkout .
      C:/git-sdk-64/mingw64/bin/gdb.exe --args C:/git-sdk-64/mingw64/bin/git.exe git_cmd options

    至此,调试环境已经完全搭建好了,可以开心的调试了。

    首先,查看 git add . 的调用过程,明确关键检查逻辑。

    明确 git add 逻辑

    在调试之前,先建立一个简单的测试环境。建立一个 UTF-16LE BOM 编码的文件 utf-l6le-bom.txt,并编辑其内容为 test。设置 .gitattributesworking-tree-encodingUTF-16LE,然后执行 git init 初始化仓库,然后就可以使用 gdb 调试 git add . 了。

    prepare-for-debugging-git-add
    prepare-for-debugging-git-add

    在打开的 shell 终端中输入如下命令 C:/git-sdk-64/mingw64/bin/gdb.exe --args C:/git-sdk-64/mingw64/bin/git.exe add . 。如果成功,会像下图这样。

    gdb-debug-git-add
    gdb-debug-git-add

    接着为函数 validate_encoding 设置断点,在 gdb 中输入 b validate_encodingbbreak 的缩写),提示设置成功后,输入 rrun)使程序重新运行起来。这时候程序应该中断下来了。

    set-breakpoint-on-validate_encoding-and-run
    set-breakpoint-on-validate_encoding-and-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 个字节的内容了。

    help-x-and-view-data
    help-x-and-view-data

    可以看到,文件内容是 UTF-16LE BOM 存储的,与磁盘上的文件内容一致。最后,看一下调用栈与之前查看源码时猜测的调用栈是否一样。输入 btbacktrace)即可查看调用栈。

    view-callstack-of-git-add
    view-callstack-of-git-add

    中间一些执行不太一样,不过也差不多,通过源码猜的调用栈应该是在其它情况下运行的。至此,执行 git add 时所做的编码格式检查就检查完了。

    接下来,要明确的是执行 git checkout . 的时候是否会调用到编码转换的函数中去,具体传递的参数是什么?

    明确 git checkout 逻辑

    首先,建立一个 UTF-16LE BOM 编码的文件 utf-l6le-bom.txt,并编辑其内容为 test。设置 .gitattributesworking-tree-encodingUTF-16,然后执行 git init && git add . && git commit -m "init"

    init-repository
    init-repository

    然后修改 utf-16le-bom.txt 的内容为 test1,然后就可以使用 gdb 调试 git checkout . 命令了。

    在打开的 shell 终端中输入如下命令

    C:/git-sdk-64/mingw64/bin/gdb.exe --args C:/git-sdk-64/mingw64/bin/git.exe checkout .

    这里就不贴图了,具体参考上面调试 git add 命令时的贴图。

    接着为函数 reencode_string_len 设置断点,在 gdb 中输入 b reencode_string_len,提示设置成功后,输入 r 使程序重新运行起来。这时候程序应该中断下来了。

    set-breakpoint-and-run
    set-breakpoint-and-run

    从图中高亮部分可知,关键参数和我们想的一样。intest,也就是第一次提交的内容。insz4,表示传入字符串的长度。out_encodingUTF-16working-tree-encoding 中指定的编码,写文件到磁盘上的编码)。in_encodingUTF-8git 内部存储数据时使用的编码)。outsz 保存了传出字符串的长度。结果字符串通过返回值返回。

    输入 bt 查看调用栈,如下:

    checkout_call_stack
    checkout_call_stack

    跟之前通过查看源码猜测的完全一样!(我这里只高亮了函数名,在每帧的结尾处,gdb 为我们列出了该函数所在的文件及对应的行数)。

    输入 fi (finish) 结束当前函数的运行,观察返回值(返回值保存了转码后的字符串)。有汇编基础的小伙伴儿都知道,一般返回值是放在 rax 中的。输入 x/10x $rax 即可按十六进制显示从 $rax 开始的 10 个字节了,如下图:

    view-out-str
    view-out-str

    可以看到,前两个字节是大端 BOM 头(0xFE 0xFF,小端 BOM 头是 0xFF 0xFE)。执行完命令后,使用十六进制查看器观察到的文件内容与调试时看到的一样,说明把编码转换后的内容写入到了磁盘中。checkout 后,磁盘文件内容下图所示:

    view-file-content-with-hex-editor
    view-file-content-with-hex-editor

    如果在调试过程中想查看相关源码,可以输入 l (list) 进行查看。下图是执行 f 0 (frame 0) 切换到 0 号栈帧后,再通过 l 命令查看源码的截图。

    switch-frame-and-view-source-using-line-command
    switch-frame-and-view-source-using-line-command

    至此,这两个疑惑都得到了圆满的解答!

    参考资料

    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 git_repository_url 下载最新一次提交,可以节省下载时间。也可以在 github 上通过 Download ZIP 按钮下载打好包的源码。
    • Source Insight 真是查看代码的神器,尤其是 Lookup References...(快捷键是 ctrl + /),可以非常方便的查看引用。按住 ctrl 后,鼠标点击函数可以跳转到函数定义的地方。
    • gdblinux 调试神器。使用 b 可以设置断点, bt 可以查看调用栈,f N 可以切换到 N 号栈帧,x 可以查看内存中的数据,i 可以查看一些信息,r 可以让程序重新运行起来(类似于 windbg 中的 g 或者 F5),l 可以列出相关的源码。
    • working-tree-encoding 与文件编码的对应关系我总结到下面的表格中了,供大家参考。
    working-tree-encoding 签出时的文件编码 git add 时支持的编码 说明
    UTF-16LE UTF-16 Little Endian (No BOM) UTF-16 Little Endian (No BOM) 文件不能带 BOM
    UTF-16BE UTF-16 Big Endian (No BOM) UTF-16 Big Endian (No BOM) 文件不能带 BOM
    UTF-16LE-BOM UTF-16 Little Endian (With BOM) UTF-16 Little Endian (With BOM) 需要带 BOM
    UTF-16BE-BOM UTF-16 Big Endian (With BOM) UTF-16 Big Endian (With BOM) 需要带 BOM
    UTF-16 UTF-16 Big Endian (With BOM) UTF-16 Big(Little) Endian (With BOM) 需要带 BOM。虽然接受 UTF-16 Little Endian,但是签出时会按 UTF-16 Big Endian处理!会导致编码改变。
    欢迎各位小伙伴指出不足,提出建议!感谢关注我的博客:)
  • 相关阅读:
    查看Android应用所需权限(uses-permission)
    Android Camera后台拍照
    傅里叶变换
    linux文件系统问题:wrong fs type, bad option, bad superblock
    H3 android 系统编译
    开源股票数据工具
    获取股票实时交易数据的方法
    获取历史和实时股票数据接口
    CRC在线计算工具
    硬盘自动挂载
  • 原文地址:https://www.cnblogs.com/bianchengnan/p/15725878.html
Copyright © 2020-2023  润新知