on
Fixing a crash in git-absorb without knowing any Rust
I fixed a crash in git-absorb
, and learned how to bisect Rust crates for the first time without knowing how to write any Rust.
I was using git-absorb
and hit a segmentation fault when trying to absorb some changes:
# in the repo with changes
$ git add -u .
$ git absorb
Segmentation fault (core dumped)
I had been using the pre-built binaries (absorb-0.6.7-x86_64-unknown-linux-musl.tar.gz
), which are statically linked against musl. However, since the pre-built binaries don't have debug symbols, I tried building from source to hopefully allow me to gather a backtrace and see where the problem was.
# in the tummychow/git-absorb repo
$ git log -n1 HEAD
219e386ff665b4fd360b397752ce51a658c5e1d6
$ cargo clean && cargo build --locked
After building, before performing any further debugging I first ensured that I could still reproduce the failure. This gave the following message:
# in the repo with changes
$ git absorb
free(): double free detected in tcache 2
Aborted (core dumped)
Running this under rust-gdb
gave the following backtrace:
>>> bt
#0 0x00007ffff7dac00b in raise () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x00007ffff7d8b859 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x00007ffff7df626e in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#3 0x00007ffff7dfe2fc in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#4 0x00007ffff7dfff6d in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#5 0x000055555564fe5b in stdalloc__free (ptr=0x5555559ee2e0) at libgit2/src/allocators/stdalloc.c:104
#6 0x0000555555611277 in git_mwindow_close_lru_window () at libgit2/src/mwindow.c:269
#7 0x00005555556114c2 in new_window (fd=7, size=92396285398, offset=49063184399) at libgit2/src/mwindow.c:337
#8 0x00005555556116df in git_mwindow_open (mwf=0x5555559d50e0, cursor=0x7fffffff1f58, offset=49063184399, extra=20, left=0x7fffffff1ee0) at libgit2/src/mwindow.c:407
#9 0x000055555564c156 in git_packfile_unpack_header (size_p=0x7fffffff1f70, type_p=0x7fffffff1f4c, mwf=0x5555559d50e0, w_curs=0x7fffffff1f58, curpos=0x7fffffff1f60) at libgit2/src/pack.c:455
#10 0x000055555564c6c2 in pack_dependency_chain (chain_out=0x7fffffff2040, cached_out=0x7fffffff2010, cached_off=0x7fffffff2920, small_stack=0x7fffffff20a0, stack_sz=0x7fffffff2018, p=0x5555559d50e0, obj_offset=49063184399) at libgit2/src/pack.c:581
#11 0x000055555564c8bc in git_packfile_unpack (obj=0x7fffffff2900, p=0x5555559d50e0, obj_offset=0x7fffffff2920) at libgit2/src/pack.c:637
#12 0x0000555555667e74 in pack_backend__read (buffer_p=0x7fffffff29b0, len_p=0x7fffffff29b8, type_p=0x7fffffff29c0, backend=0x5555559c4f90, oid=0x555555abb145) at libgit2/src/odb_pack.c:400
#13 0x000055555564292d in odb_read_1 (out=0x7fffffff2a88, db=0x5555559ca3d0, id=0x555555abb145, only_refreshed=false) at libgit2/src/odb.c:1065
#14 0x0000555555642b3c in git_odb_read (out=0x7fffffff2a88, db=0x5555559ca3d0, id=0x555555abb145) at libgit2/src/odb.c:1116
#15 0x0000555555611ff5 in git_object_lookup_prefix (object_out=0x7fffffff2b50, repo=0x5555559b4fe0, id=0x555555abb145, len=40, type=GIT_OBJECT_TREE) at libgit2/src/object.c:222
#16 0x00005555556120c7 in git_object_lookup (object_out=0x7fffffff2b50, repo=0x5555559b4fe0, id=0x555555abb145, type=GIT_OBJECT_TREE) at libgit2/src/object.c:253
#17 0x00005555556097aa in git_tree_lookup (out=0x7fffffff2b50, repo=0x5555559b4fe0, id=0x555555abb145) at libgit2/src/object_api.c:56
#18 0x000055555566ab6c in tree_iterator_frame_push (iter=0x5555559cdd70, entry=0x555555ab6c48) at libgit2/src/iterator.c:660
#19 0x000055555566b130 in tree_iterator_advance (out=0x7fffffff2ca8, i=0x5555559cdd70) at libgit2/src/iterator.c:813
#20 0x0000555555625006 in git_iterator_advance (entry=0x7fffffff2ca8, iter=0x5555559cdd70) at libgit2/src/iterator.h:184
#21 0x000055555562739c in iterator_advance (entry=0x7fffffff2ca8, iterator=0x5555559cdd70) at libgit2/src/diff_generate.c:925
#22 0x0000555555627aaf in handle_matched_item (diff=0x5555559cb200, info=0x7fffffff2c90) at libgit2/src/diff_generate.c:1179
#23 0x0000555555627cf7 in git_diff__from_iterators (out=0x7fffffff2d20, repo=0x5555559b4fe0, old_iter=0x5555559cdd70, new_iter=0x5555559ca200, opts=0x7fffffff3730) at libgit2/src/diff_generate.c:1249
#24 0x00005555556280a0 in git_diff_tree_to_tree (out=0x7fffffff2e08, repo=0x5555559b4fe0, old_tree=0x5555559d8f50, new_tree=0x5555559cc120, opts=0x7fffffff3730) at libgit2/src/diff_generate.c:1319
#25 0x00005555555eb58e in git2::repo::Repository::diff_tree_to_tree (self=0x7fffffff34c0, old_tree=..., new_tree=..., opts=...) at src/repo.rs:2374
#26 0x00005555555b5791 in git_absorb::run (config=0x7fffffffce00) at src/lib.rs:42
#27 0x000055555559c416 in git_absorb::main () at src/main.rs:102
Here we see that the backtrace points to libgit2.
Looking at the Cargo.lock
file, I saw that the libgit2-sys
crate was rather old compared to the latest on crates.io.
So I first tried updating the crate to see if someone had already fixed the issue upstream:
# in the tummychow/git-absorb repo
$ cargo clean && cargo update && cargo build --locked
This updated the transitive libgit2-sys
dependency in the Cargo.lock file from 0.12.13+1.0.1
to 0.12.26+1.3.0
.
name = "libgit2-sys"
-version = "0.12.13+1.0.1"
+version = "0.12.26+1.3.0"
Then running the newly built binary with the updated libgit2
succeeded:
# in the repo with changes
$ git absorb
(success)
Since this worked, it indicated that the issue I was seeing was fixed upstream already.
I then tried explicitly adding libgit2-sys
as a dependency and setting an exact version to override the Cargo dependency resolver (without updating git2
). The first libgit2-sys
after the current version in the Cargo.lock
file that fixes this seems to be =0.12.14+1.1.0
.
That release bumps the libgit2
dependency, so most likely this was fixed by some change in libgit2
between 1.0.0 and 1.1.0.
I then set up the libgit2
repository locally to replace the libgit2-sys
, and bisecting between those two tags indeed led to da3288ded5bce2a37566126be51fe75ff0316c2d
.