| /* |
| * Copyright (C) the libgit2 contributors. All rights reserved. |
| * |
| * This file is part of libgit2, distributed under the GNU GPL v2 with |
| * a Linking Exception. For full terms see the included COPYING file. |
| */ |
| |
| #if defined(GIT_MSVC_CRTDBG) |
| #include "Windows.h" |
| #include "Dbghelp.h" |
| #include "win32/posix.h" |
| #include "w32_stack.h" |
| #include "hash.h" |
| |
| /** |
| * This is supposedly defined in WinBase.h (from Windows.h) but there were linker issues. |
| */ |
| USHORT WINAPI RtlCaptureStackBackTrace(ULONG, ULONG, PVOID*, PULONG); |
| |
| static bool g_win32_stack_initialized = false; |
| static HANDLE g_win32_stack_process = INVALID_HANDLE_VALUE; |
| static git_win32__stack__aux_cb_alloc g_aux_cb_alloc = NULL; |
| static git_win32__stack__aux_cb_lookup g_aux_cb_lookup = NULL; |
| |
| int git_win32__stack__set_aux_cb( |
| git_win32__stack__aux_cb_alloc cb_alloc, |
| git_win32__stack__aux_cb_lookup cb_lookup) |
| { |
| g_aux_cb_alloc = cb_alloc; |
| g_aux_cb_lookup = cb_lookup; |
| |
| return 0; |
| } |
| |
| void git_win32__stack_init(void) |
| { |
| if (!g_win32_stack_initialized) { |
| g_win32_stack_process = GetCurrentProcess(); |
| SymSetOptions(SYMOPT_LOAD_LINES); |
| SymInitialize(g_win32_stack_process, NULL, TRUE); |
| g_win32_stack_initialized = true; |
| } |
| } |
| |
| void git_win32__stack_cleanup(void) |
| { |
| if (g_win32_stack_initialized) { |
| SymCleanup(g_win32_stack_process); |
| g_win32_stack_process = INVALID_HANDLE_VALUE; |
| g_win32_stack_initialized = false; |
| } |
| } |
| |
| int git_win32__stack_capture(git_win32__stack__raw_data *pdata, int skip) |
| { |
| if (!g_win32_stack_initialized) { |
| giterr_set(GITERR_INVALID, "git_win32_stack not initialized."); |
| return GIT_ERROR; |
| } |
| |
| memset(pdata, 0, sizeof(*pdata)); |
| pdata->nr_frames = RtlCaptureStackBackTrace( |
| skip+1, GIT_WIN32__STACK__MAX_FRAMES, pdata->frames, NULL); |
| |
| /* If an "aux" data provider was registered, ask it to capture |
| * whatever data it needs and give us an "aux_id" to it so that |
| * we can refer to it later when reporting. |
| */ |
| if (g_aux_cb_alloc) |
| (g_aux_cb_alloc)(&pdata->aux_id); |
| |
| return 0; |
| } |
| |
| int git_win32__stack_compare( |
| git_win32__stack__raw_data *d1, |
| git_win32__stack__raw_data *d2) |
| { |
| return memcmp(d1, d2, sizeof(*d1)); |
| } |
| |
| int git_win32__stack_format( |
| char *pbuf, int buf_len, |
| const git_win32__stack__raw_data *pdata, |
| const char *prefix, const char *suffix) |
| { |
| #define MY_MAX_FILENAME 255 |
| |
| /* SYMBOL_INFO has char FileName[1] at the end. The docs say to |
| * to malloc it with extra space for your desired max filename. |
| */ |
| struct { |
| SYMBOL_INFO symbol; |
| char extra[MY_MAX_FILENAME + 1]; |
| } s; |
| |
| IMAGEHLP_LINE64 line; |
| int buf_used = 0; |
| unsigned int k; |
| char detail[MY_MAX_FILENAME * 2]; /* filename plus space for function name and formatting */ |
| int detail_len; |
| |
| if (!g_win32_stack_initialized) { |
| giterr_set(GITERR_INVALID, "git_win32_stack not initialized."); |
| return GIT_ERROR; |
| } |
| |
| if (!prefix) |
| prefix = "\t"; |
| if (!suffix) |
| suffix = "\n"; |
| |
| memset(pbuf, 0, buf_len); |
| |
| memset(&s, 0, sizeof(s)); |
| s.symbol.MaxNameLen = MY_MAX_FILENAME; |
| s.symbol.SizeOfStruct = sizeof(SYMBOL_INFO); |
| |
| memset(&line, 0, sizeof(line)); |
| line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); |
| |
| for (k=0; k < pdata->nr_frames; k++) { |
| DWORD64 frame_k = (DWORD64)pdata->frames[k]; |
| DWORD dwUnused; |
| |
| if (SymFromAddr(g_win32_stack_process, frame_k, 0, &s.symbol) && |
| SymGetLineFromAddr64(g_win32_stack_process, frame_k, &dwUnused, &line)) { |
| const char *pslash; |
| const char *pfile; |
| |
| pslash = strrchr(line.FileName, '\\'); |
| pfile = ((pslash) ? (pslash+1) : line.FileName); |
| p_snprintf(detail, sizeof(detail), "%s%s:%d> %s%s", |
| prefix, pfile, line.LineNumber, s.symbol.Name, suffix); |
| } else { |
| /* This happens when we cross into another module. |
| * For example, in CLAR tests, this is typically |
| * the CRT startup code. Just print an unknown |
| * frame and continue. |
| */ |
| p_snprintf(detail, sizeof(detail), "%s??%s", prefix, suffix); |
| } |
| detail_len = strlen(detail); |
| |
| if (buf_len < (buf_used + detail_len + 1)) { |
| /* we don't have room for this frame in the buffer, so just stop. */ |
| break; |
| } |
| |
| memcpy(&pbuf[buf_used], detail, detail_len); |
| buf_used += detail_len; |
| } |
| |
| /* "aux_id" 0 is reserved to mean no aux data. This is needed to handle |
| * allocs that occur before the aux callbacks were registered. |
| */ |
| if (pdata->aux_id > 0) { |
| p_snprintf(detail, sizeof(detail), "%saux_id: %d%s", |
| prefix, pdata->aux_id, suffix); |
| detail_len = strlen(detail); |
| if ((buf_used + detail_len + 1) < buf_len) { |
| memcpy(&pbuf[buf_used], detail, detail_len); |
| buf_used += detail_len; |
| } |
| |
| /* If an "aux" data provider is still registered, ask it to append its detailed |
| * data to the end of ours using the "aux_id" it gave us when this de-duped |
| * item was created. |
| */ |
| if (g_aux_cb_lookup) |
| (g_aux_cb_lookup)(pdata->aux_id, &pbuf[buf_used], (buf_len - buf_used - 1)); |
| } |
| |
| return GIT_OK; |
| } |
| |
| int git_win32__stack( |
| char * pbuf, int buf_len, |
| int skip, |
| const char *prefix, const char *suffix) |
| { |
| git_win32__stack__raw_data data; |
| int error; |
| |
| if ((error = git_win32__stack_capture(&data, skip)) < 0) |
| return error; |
| if ((error = git_win32__stack_format(pbuf, buf_len, &data, prefix, suffix)) < 0) |
| return error; |
| return 0; |
| } |
| |
| #endif |