Skip to content

Commit c6b70b1

Browse files
committed
Use libbacktrace to debug file locking issues
On Windows, it is not possible to overwrite files as long as they are in use. This is particularly funny (not!) when the same process still holds open file handles to that file, and it notoriously hard to debug. Let's add a compile-time option to output stacktraces to the offending calls when we fail to overwrite/remove them. Note: it has to be a compile time option because we would have to turn off ASLR otherwise (and it is too good of a first line of defense to just turn off). Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 37ad56f commit c6b70b1

File tree

4 files changed

+266
-0
lines changed

4 files changed

+266
-0
lines changed

Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,7 @@ BUILTIN_OBJS += builtin/write-tree.o
12461246
# upstream unnecessarily (making merging in future changes easier).
12471247
THIRD_PARTY_SOURCES += compat/inet_ntop.c
12481248
THIRD_PARTY_SOURCES += compat/inet_pton.c
1249+
THIRD_PARTY_SOURCES += compat/libbacktrace/%
12491250
THIRD_PARTY_SOURCES += compat/nedmalloc/%
12501251
THIRD_PARTY_SOURCES += compat/obstack.%
12511252
THIRD_PARTY_SOURCES += compat/poll/%
@@ -1618,6 +1619,15 @@ ifdef OPEN_RETURNS_EINTR
16181619
COMPAT_CFLAGS += -DOPEN_RETURNS_EINTR
16191620
COMPAT_OBJS += compat/open.o
16201621
endif
1622+
ifneq (,$(DEBUG_FILE_LOCKS))
1623+
BACKTRACE_SOURCES := $(patsubst %,compat/libbacktrace/%,atomic.c \
1624+
alloc.c dwarf.c state.c fileline.c posix.c pecoff.c sort.c \
1625+
read.c)
1626+
BACKTRACE_OBJS := $(patsubst %.c,%.o,$(BACKTRACE_SOURCES))
1627+
COMPAT_OBJS += $(BACKTRACE_OBJS)
1628+
$(BACKTRACE_OBJS): EXTRA_CPPFLAGS = -I compat/libbacktrace
1629+
COMPAT_CFLAGS += -DDEBUG_FILE_LOCKS
1630+
endif
16211631
ifdef NO_SYMLINK_HEAD
16221632
BASIC_CFLAGS += -DNO_SYMLINK_HEAD
16231633
endif

compat/mingw.c

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,174 @@
1818
#include "../attr.h"
1919
#include "../string-list.h"
2020

21+
#ifdef DEBUG_FILE_LOCKS
22+
23+
#include "libbacktrace/backtrace.h"
24+
#include "../hashmap.h"
25+
26+
struct file_lock_backtrace
27+
{
28+
struct hashmap_entry entry;
29+
int fd, count;
30+
uintptr_t *pcs;
31+
const char *filename;
32+
};
33+
34+
static CRITICAL_SECTION backtrace_mutex;
35+
static struct hashmap file_lock_map;
36+
#define FILE_LOCK_MAX_FD 256
37+
static struct file_lock_backtrace *file_lock_by_fd[FILE_LOCK_MAX_FD];
38+
39+
static int my_backtrace_cb(void *data, uintptr_t pc, const char *filename,
40+
int lineno, const char *function)
41+
{
42+
struct strbuf *buf = data;
43+
44+
if (!function || !strcmp("__tmainCRTStartup", function))
45+
return -1;
46+
47+
strbuf_addf(buf, "%s:%d:\n\t%s\n", filename, lineno, function);
48+
49+
return 0;
50+
}
51+
52+
static void my_error_cb(void *data, const char *msg, int errnum)
53+
{
54+
struct strbuf *buf = data;
55+
56+
strbuf_addf(buf, "error %s (%d)\n", msg, errnum);
57+
}
58+
59+
static void file_lock_backtrace(struct file_lock_backtrace *data,
60+
struct strbuf *buf)
61+
{
62+
static struct backtrace_state *state;
63+
static int initialized;
64+
int i;
65+
66+
if (!initialized) {
67+
EnterCriticalSection(&backtrace_mutex);
68+
if (!initialized) {
69+
state = backtrace_create_state(NULL, 1, my_error_cb,
70+
NULL);
71+
initialized = 1;
72+
}
73+
LeaveCriticalSection(&backtrace_mutex);
74+
}
75+
76+
if (data->fd >= 0)
77+
strbuf_addf(buf, "file '%s' (fd %d) was opened here:\n",
78+
data->filename, data->fd);
79+
for (i = 0; i < data->count; i++)
80+
if (backtrace_pcinfo(state, data->pcs[i], my_backtrace_cb,
81+
my_error_cb, buf) < 0)
82+
break;
83+
}
84+
85+
static struct file_lock_backtrace *alloc_file_lock_backtrace(int fd,
86+
const char *filename)
87+
{
88+
DECLARE_PROC_ADDR(kernel32.dll, USHORT, NTAPI, RtlCaptureStackBackTrace,
89+
ULONG, ULONG, PVOID*, PULONG);
90+
struct file_lock_backtrace *result;
91+
uintptr_t pcs[62];
92+
int count = 0;
93+
size_t pcs_size = 0, size, len = strlen(filename) + 1;
94+
95+
if ((fd < 0 || fd >= FILE_LOCK_MAX_FD) && fd != -123)
96+
BUG("Called with fd = %d\n", fd);
97+
98+
if (INIT_PROC_ADDR(RtlCaptureStackBackTrace)) {
99+
count = RtlCaptureStackBackTrace(1, ARRAY_SIZE(pcs),
100+
(void **)pcs, NULL);
101+
pcs_size = sizeof(uintptr_t) * count;
102+
}
103+
size = sizeof(*result) + pcs_size + len + 1;
104+
105+
result = xmalloc(size);
106+
result->fd = fd;
107+
result->count = count;
108+
if (!count)
109+
result->pcs = NULL;
110+
else {
111+
result->pcs = (uintptr_t *)((char *)result + sizeof(*result));
112+
memcpy(result->pcs, pcs, pcs_size);
113+
}
114+
115+
result->filename = ((char *)result + sizeof(*result) + pcs_size);
116+
strlcpy((char *)result->filename, filename, len);
117+
118+
if (fd < 0)
119+
return result;
120+
121+
EnterCriticalSection(&backtrace_mutex);
122+
if (file_lock_by_fd[fd]) {
123+
struct strbuf buf = STRBUF_INIT;
124+
strbuf_addf(&buf, "Bogus file_lock (%d). First trace:\n", fd);
125+
file_lock_backtrace(file_lock_by_fd[fd], &buf);
126+
strbuf_addf(&buf, "\nSecond trace:\n");
127+
file_lock_backtrace(result, &buf);
128+
BUG("%s", buf.buf);
129+
}
130+
file_lock_by_fd[fd] = result;
131+
hashmap_entry_init(&result->entry, strihash(filename));
132+
hashmap_add(&file_lock_map, &result->entry);
133+
LeaveCriticalSection(&backtrace_mutex);
134+
135+
return result;
136+
}
137+
138+
static void current_backtrace(struct strbuf *buf)
139+
{
140+
struct file_lock_backtrace *p = alloc_file_lock_backtrace(-123, "");
141+
file_lock_backtrace(p, buf);
142+
free(p);
143+
}
144+
145+
static void remove_file_lock_backtrace(int fd)
146+
{
147+
if (fd < 0 || fd >= FILE_LOCK_MAX_FD)
148+
BUG("Called with fd = %d\n", fd);
149+
150+
EnterCriticalSection(&backtrace_mutex);
151+
if (!file_lock_by_fd[fd])
152+
BUG("trying to release non-existing lock for fd %d", fd);
153+
154+
hashmap_remove(&file_lock_map, &file_lock_by_fd[fd]->entry, NULL);
155+
free(file_lock_by_fd[fd]);
156+
file_lock_by_fd[fd] = NULL;
157+
LeaveCriticalSection(&backtrace_mutex);
158+
}
159+
160+
static int file_lock_backtrace_cmp(const void *dummy,
161+
const struct file_lock_backtrace *a,
162+
const struct file_lock_backtrace *b,
163+
const void *keydata)
164+
{
165+
return strcasecmp(a->filename,
166+
keydata ? (const char *)keydata : b->filename);
167+
}
168+
169+
static struct file_lock_backtrace *file_lock_lookup(const char *filename)
170+
{
171+
struct file_lock_backtrace entry, *result;
172+
173+
hashmap_entry_init(&entry.entry, strihash(filename));
174+
EnterCriticalSection(&backtrace_mutex);
175+
result = hashmap_get_entry(&file_lock_map, &entry, entry, filename);
176+
LeaveCriticalSection(&backtrace_mutex);
177+
178+
return result;
179+
}
180+
181+
static void initialize_file_lock_map(void)
182+
{
183+
InitializeCriticalSection(&backtrace_mutex);
184+
hashmap_init(&file_lock_map, (hashmap_cmp_fn)file_lock_backtrace_cmp,
185+
NULL, 0);
186+
}
187+
#endif
188+
21189
#define HCAST(type, handle) ((type)(intptr_t)handle)
22190

23191
void open_in_gdb(void)
@@ -529,6 +697,21 @@ int mingw_unlink(const char *pathname)
529697
*/
530698
if (!_wrmdir(wpathname))
531699
return 0;
700+
#ifdef DEBUG_FILE_LOCKS
701+
{
702+
struct file_lock_backtrace *p =
703+
file_lock_lookup(pathname);
704+
if (p) {
705+
struct strbuf buf = STRBUF_INIT;
706+
strbuf_addf(&buf, "the file '%s' wants "
707+
"to be deleted here:\n", pathname);
708+
current_backtrace(&buf);
709+
strbuf_addf(&buf, "\nBut it is still open:\n");
710+
file_lock_backtrace(p, &buf);
711+
die("%s\n", buf.buf);
712+
}
713+
}
714+
#endif
532715
} while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. "
533716
"Should I try again?", pathname));
534717
return -1;
@@ -803,6 +986,10 @@ int mingw_open (const char *filename, int oflags, ...)
803986
if (fd >= 0 && set_hidden_flag(wfilename, 1))
804987
warning("could not mark '%s' as hidden.", filename);
805988
}
989+
#ifdef DEBUG_FILE_LOCKS
990+
if (fd >= 0)
991+
alloc_file_lock_backtrace(fd, filename);
992+
#endif
806993
return fd;
807994
}
808995

@@ -858,6 +1045,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
8581045
errno = ENOENT;
8591046
if (file && hide && set_hidden_flag(wfilename, 1))
8601047
warning("could not mark '%s' as hidden.", filename);
1048+
#ifdef DEBUG_FILE_LOCKS
1049+
if (file)
1050+
alloc_file_lock_backtrace(fileno(file), filename);
1051+
#endif
8611052
return file;
8621053
}
8631054

@@ -866,6 +1057,9 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
8661057
int hide = needs_hiding(filename);
8671058
FILE *file;
8681059
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
1060+
#ifdef DEBUG_FILE_LOCKS
1061+
int oldfd = fileno(stream);
1062+
#endif
8691063
if (filename && !strcmp(filename, "/dev/null"))
8701064
wcscpy(wfilename, L"nul");
8711065
else if (!is_valid_win32_path(filename, 1)) {
@@ -885,9 +1079,37 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
8851079
file = _wfreopen(wfilename, wotype, stream);
8861080
if (file && hide && set_hidden_flag(wfilename, 1))
8871081
warning("could not mark '%s' as hidden.", filename);
1082+
#ifdef DEBUG_FILE_LOCKS
1083+
if (file) {
1084+
remove_file_lock_backtrace(oldfd);
1085+
alloc_file_lock_backtrace(fileno(file), filename);
1086+
}
1087+
#endif
8881088
return file;
8891089
}
8901090

1091+
#ifdef DEBUG_FILE_LOCKS
1092+
#undef close
1093+
int mingw_close(int fd)
1094+
{
1095+
int ret = close(fd);
1096+
if (!ret)
1097+
remove_file_lock_backtrace(fd);
1098+
return ret;
1099+
}
1100+
#define close mingw_close
1101+
1102+
#undef fclose
1103+
int mingw_fclose(FILE *stream)
1104+
{
1105+
int fd = fileno(stream), ret = fclose(stream);
1106+
if (!ret)
1107+
remove_file_lock_backtrace(fd);
1108+
return ret;
1109+
}
1110+
#define fclose mingw_fclose
1111+
#endif
1112+
8911113
#undef fflush
8921114
int mingw_fflush(FILE *stream)
8931115
{
@@ -2698,6 +2920,26 @@ int mingw_rename(const char *pold, const char *pnew)
26982920
SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
26992921
goto repeat;
27002922
}
2923+
#ifdef DEBUG_FILE_LOCKS
2924+
{
2925+
struct file_lock_backtrace *p = file_lock_lookup(pnew);
2926+
const char *which = "target";
2927+
if (!p) {
2928+
p = file_lock_lookup(pold);
2929+
which = "source";
2930+
}
2931+
if (p) {
2932+
struct strbuf buf = STRBUF_INIT;
2933+
strbuf_addf(&buf, "the file '%s' wants to be "
2934+
"renamed to '%s' here:\n", pold, pnew);
2935+
current_backtrace(&buf);
2936+
strbuf_addf(&buf, "\nBut the %s is still open:\n",
2937+
which);
2938+
file_lock_backtrace(p, &buf);
2939+
die("%s\n", buf.buf);
2940+
}
2941+
}
2942+
#endif
27012943
if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
27022944
"Should I try again?", pold, pnew))
27032945
goto repeat;
@@ -3954,6 +4196,10 @@ int wmain(int argc, const wchar_t **wargv)
39544196

39554197
SetConsoleCtrlHandler(handle_ctrl_c, TRUE);
39564198

4199+
#ifdef DEBUG_FILE_LOCKS
4200+
initialize_file_lock_map();
4201+
#endif
4202+
39574203
maybe_redirect_std_handles();
39584204
adjust_symlink_flags();
39594205
fsync_object_files = 1;

compat/mingw.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,14 @@ int mingw_fflush(FILE *stream);
247247
ssize_t mingw_write(int fd, const void *buf, size_t len);
248248
#define write mingw_write
249249

250+
#ifdef DEBUG_FILE_LOCKS
251+
int mingw_close(int fd);
252+
#define close mingw_close
253+
254+
int mingw_fclose(FILE *stream);
255+
#define fclose mingw_fclose
256+
#endif
257+
250258
int mingw_access(const char *filename, int mode);
251259
#undef access
252260
#define access mingw_access

config.mak.uname

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,9 +699,11 @@ else
699699
# Enable DEP
700700
BASIC_LDFLAGS += -Wl,--nxcompat
701701
# Enable ASLR (unless debugging)
702+
ifeq (,$(DEBUG_FILE_LOCKS))
702703
ifneq (,$(findstring -O,$(filter-out -O0 -Og,$(CFLAGS))))
703704
BASIC_LDFLAGS += -Wl,--dynamicbase
704705
endif
706+
endif
705707
ifeq (MINGW32,$(MSYSTEM))
706708
prefix = /mingw32
707709
HOST_CPU = i686

0 commit comments

Comments
 (0)