From: David Stacey <drstacey@tiscali.co.uk>
To: cygwin-developers@cygwin.com
Subject: backtrace(3) in Cygwin
Date: Sun, 15 Mar 2015 19:52:00 -0000 [thread overview]
Message-ID: <5505E2DA.6090806@tiscali.co.uk> (raw)
[-- Attachment #1: Type: text/plain, Size: 598 bytes --]
Following on from my thread on the main list [1], I have made an initial
attempt at implementing backtrace(3) and backtrace_symbols(3). This
still needs a little work - at the moment, the paths in the strings will
be DOS-style rather than POSIX. However, I wanted to submit an early
version to make sure you were happy with my approach before I spend more
time on it.
I've also documented (in the code) the issues surrounding
backtrace_symbols_fd(3) on Cygwin.
This is my first real submission to the programme - be kind :-)
Dave.
[1] - http://cygwin.com/ml/cygwin/2015-03/msg00198.html
[-- Attachment #2: exec_info.c --]
[-- Type: text/x-csrc, Size: 7786 bytes --]
#include <windows.h>
#include <dbghelp.h>
#include <assert.h>
#include <stdio.h>
/* This implementation of backtrace(3) and backtrace_symbols(3) comes with a
* number of limitations:
*
* - The maxium number of stack frames is limited by the implementation of
* CaptureStackBackTrace() in Windows XP, which can only retrieve a
* maximum of 63 frames.
*
* - All of the DbgHelp functions in Windows are not thread safe. This
* means that our implementation of the backtrace functions are also not
* thread safe.
*
* - The frames returned by this implementation of backtrace(3) include
* those those in the Windows kernel. This means that kernel32.dll and
* ntdll.dll appear at the bottom end of the stack trace, and obviously
* you wouldn't see these on Linux.
*/
/* backtrace() returns a backtrace for the calling program, in the array
* pointed to by buffer. A backtrace is the series of currently active
* function calls for the program. Each item in the array pointed to by
* buffer is of type void *, and is the return address from the corresponding
* stack frame. The size argument specifies the maximum number of addresses
* that can be stored in buffer. If the backtrace is larger than size, then
* the addresses corresponding to the size most recent function calls are
* returned; to obtain the complete backtrace, make sure that buffer and size
* are large enough.
*/
int backtrace(void **buffer, int size)
{
HANDLE process = GetCurrentProcess();
const int xp_max_frame_depth = 61;
if (size > xp_max_frame_depth)
size = xp_max_frame_depth;
/* Ignore this function when getting the stack frames. */
SymInitialize(process, NULL, TRUE);
return CaptureStackBackTrace(1, size, buffer, NULL);
}
/* Given the set of addresses returned by backtrace() in buffer,
* backtrace_symbols() translates the addresses into an array of strings that
* describe the addresses symbolically. The size argument specifies the
* number of addresses in buffer. The symbolic representation of each address
* consists of the function name (if this can be determined), a hexadecimal
* offset into the function, and the actual return address (in hexadecimal).
* The address of the array of string pointers is returned as the function
* result of backtrace_symbols(). This array is malloc(3)ed by
* backtrace_symbols(), and must be freed by the caller. (The strings pointed
* to by the array of pointers need not and should not be freed.)
*/
char **backtrace_symbols(void *const *buffer, int size)
{
HANDLE process = GetCurrentProcess();
SYMBOL_INFO *symbol;
IMAGEHLP_MODULE64 module_info;
const int chars_needed_to_display_address = 16;
const int additional_characters = 11;
int chars_required = 0;
int i;
char **result;
DWORD64 offset;
char* frame_text;
symbol = (SYMBOL_INFO*)calloc(sizeof(SYMBOL_INFO) + (MAX_SYM_NAME + 1) * sizeof(char), 1);
symbol->MaxNameLen = MAX_SYM_NAME;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
module_info.SizeOfStruct = sizeof(IMAGEHLP_MODULE64);
/* The glibc documentation doen't say whether the strings we're preparing
* should contain decorated or undecorated symbol names; it simply states
* that it should be a 'printable representation' of the frame. The man
* page doesn't offer any guidance either. We'll use undecorated symbols,
* as these are probably more helpful.
*/
SymSetOptions(SymGetOptions() | SYMOPT_UNDNAME);
/* Compute the amount of memory required to hold the results. Each string
* takes the following form:
*
* ./prog(myfunc+0x21) [0x8048894]
*
* So we need the module name and the symbol name (which will vary in
* length), two addresses (which we assume are 64 bits and therefore
* require 16 characters each), ten other characters (brackets, spaces,
* etc.) and a terminating NULL.
*
* We also need a string lookup table, which contains pointers to these
* strings. The string lookup table comes first in memory, followed by
* the strings themselves.
*/
chars_required = size * sizeof(char*);
for (i = 0; i < size; ++i)
{
if ((SymFromAddr(process, (DWORD64)(buffer[i]), 0, symbol)) &&
(SymGetModuleInfo64(process, symbol->Address, &module_info)))
{
chars_required += strlen(symbol->Name) + strlen(module_info.LoadedImageName) +
(2 * chars_needed_to_display_address) + additional_characters;
}
}
/* Allocate enough memory to hold the strings and the string lookup
* table. This memory buffer is returned once it has been populated, and
* it is the responsibility of the caller to free(3) the memory.
*/
result = (char**) malloc(chars_required);
/* Now populate the string lookup table and the strings with the text
* describing a frame. The pointer 'frame_text' is within the buffer we
* have just allocated and points to the start of the next string to
* write.
*/
if (result)
{
frame_text = (char*) (result + size);
for (i = 0; i < size; ++i)
{
result[i] = frame_text;
if ((SymFromAddr(process, (DWORD64)(buffer[i]), &offset, symbol)) &&
(SymGetModuleInfo64(process, symbol->Address, &module_info)))
{
frame_text += 1 + sprintf(frame_text, "%s(%s+0x%lx) [0x%lx]",
module_info.LoadedImageName, symbol->Name, (unsigned long)offset,
(unsigned long)buffer[i]);
}
else
frame_text += 1 + sprintf(frame_text, "[0x%lx]", (unsigned long)buffer[i]);
}
assert(frame_text < (char*)result + chars_required);
}
free(symbol);
return result;
}
/* backtrace_symbols_fd() takes the same buffer and size arguments as
* backtrace_symbols(), but instead of returning an array of strings to the
* caller, it writes the strings, one per line, to the file descriptor fd.
* backtrace_symbols_fd() does not call malloc(3), and so can be employed in
* situations where the latter function might fail.
*/
void backtrace_symbols_fd(void *const *buffer, int size, int fd)
{
/* A Cygwin implementation of backtrace_symbols_fd(3) is going to be
* somewhat problematic and will demand a compromise at some point along
* the way. The problem is how to allocate memory for the SYMBOL_INFO
* structure, given that we're not supposed to use heap memory. Windows
* defines MAX_SYM_NAME as 2000 characters, and clearly we shouldn't go
* trying to allocate that much memory on the stack.
*
* Then there's the issue of how we actually get the data out to the file
* descriptor supplied. If Cygwin supports dprintf(3) then that's all
* well and good - but if not we'll have to use write(2), and that
* involves allocating another buffer to hold the text we want to write
* out - and that means a second copy of our long symbol name on the
* stack.
*
* Clearly a compromise is needed. Here are some options:
*
* - Instead of MAX_SYM_NAME, use a much, much smaller value (say
* 256). Then we could allocate all the memory we need on the stack
* and still display the majority of symbol names.
*
* - Allocate heap memory anyway, irrespective of what the man page
* says. In environments where Cygwin is run (i.e. the Windows
* desktop), heap is probably plentiful, so does this matter?
*
* - Do not provide backtrace_symbols_fd(3) at all.
*
* For the moment, I'll take the latter of those options.
*/
}
next reply other threads:[~2015-03-15 19:52 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2015-03-15 19:52 David Stacey [this message]
2015-03-16 10:07 ` Corinna Vinschen
2015-03-16 22:22 ` David Stacey
2015-03-17 8:57 ` Corinna Vinschen
2015-03-20 19:15 ` Corinna Vinschen
2015-03-20 23:59 ` David Stacey
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5505E2DA.6090806@tiscali.co.uk \
--to=drstacey@tiscali.co.uk \
--cc=cygwin-developers@cygwin.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).