1 | /* |
2 | Copyright (c) 2001, 2010, Oracle and/or its affiliates |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of the GNU General Public License as published by |
6 | the Free Software Foundation; version 2 of the License. |
7 | |
8 | This program is distributed in the hope that it will be useful, |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | GNU General Public License for more details. |
12 | |
13 | You should have received a copy of the GNU General Public License |
14 | along with this program; if not, write to the Free Software |
15 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ |
16 | |
17 | /* Resolve numeric stack dump produced by mysqld 3.23.30 and later |
18 | versions into symbolic names. By Sasha Pachev <sasha@mysql.com> |
19 | */ |
20 | |
21 | #include <my_global.h> |
22 | #include <m_ctype.h> |
23 | #include <my_sys.h> |
24 | #include <m_string.h> |
25 | #include <mysql_version.h> |
26 | #include <errno.h> |
27 | #include <my_getopt.h> |
28 | |
29 | #define INIT_SYM_TABLE 4096 |
30 | #define INC_SYM_TABLE 4096 |
31 | #define MAX_SYM_SIZE 128 |
32 | #define DUMP_VERSION "1.4" |
33 | #define HEX_INVALID (uchar)255 |
34 | |
35 | typedef ulong my_long_addr_t ; /* at some point, we need to fix configure |
36 | * to define this for us |
37 | */ |
38 | |
39 | typedef struct sym_entry |
40 | { |
41 | char symbol[MAX_SYM_SIZE]; |
42 | uchar* addr; |
43 | } SYM_ENTRY; |
44 | |
45 | |
46 | static char* dump_fname = 0, *sym_fname = 0; |
47 | static DYNAMIC_ARRAY sym_table; /* how do you like this , static DYNAMIC ? */ |
48 | static FILE* fp_dump, *fp_sym = 0, *fp_out; |
49 | |
50 | static struct my_option my_long_options[] = |
51 | { |
52 | {"help" , 'h', "Display this help and exit." , |
53 | 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, |
54 | {"version" , 'V', "Output version information and exit." , |
55 | 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, |
56 | {"symbols-file" , 's', "Use specified symbols file." , &sym_fname, |
57 | &sym_fname, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, |
58 | {"numeric-dump-file" , 'n', "Read the dump from specified file." , |
59 | &dump_fname, &dump_fname, 0, GET_STR, REQUIRED_ARG, |
60 | 0, 0, 0, 0, 0, 0}, |
61 | { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} |
62 | }; |
63 | |
64 | |
65 | static void verify_sort(); |
66 | |
67 | |
68 | static void print_version(void) |
69 | { |
70 | printf("%s Ver %s Distrib %s, for %s (%s)\n" ,my_progname,DUMP_VERSION, |
71 | MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); |
72 | } |
73 | |
74 | |
75 | static void usage() |
76 | { |
77 | print_version(); |
78 | printf("MySQL AB, by Sasha Pachev\n" ); |
79 | printf("This software comes with ABSOLUTELY NO WARRANTY\n\n" ); |
80 | printf("Resolve numeric stack strace dump into symbols.\n\n" ); |
81 | printf("Usage: %s [OPTIONS] symbols-file [numeric-dump-file]\n" , |
82 | my_progname); |
83 | my_print_help(my_long_options); |
84 | my_print_variables(my_long_options); |
85 | printf("\n\ |
86 | The symbols-file should include the output from: 'nm --numeric-sort mysqld'.\n\ |
87 | The numeric-dump-file should contain a numeric stack trace from mysqld.\n\ |
88 | If the numeric-dump-file is not given, the stack trace is read from stdin.\n" ); |
89 | } |
90 | |
91 | |
92 | static void die(const char* fmt, ...) |
93 | { |
94 | va_list args; |
95 | va_start(args, fmt); |
96 | fprintf(stderr, "%s: " , my_progname); |
97 | vfprintf(stderr, fmt, args); |
98 | fprintf(stderr, "\n" ); |
99 | va_end(args); |
100 | exit(1); |
101 | } |
102 | |
103 | |
104 | static my_bool |
105 | get_one_option(int optid, const struct my_option *opt __attribute__((unused)), |
106 | char *argument __attribute__((unused))) |
107 | { |
108 | switch(optid) { |
109 | case 'V': |
110 | print_version(); |
111 | exit(0); |
112 | case '?': |
113 | usage(); |
114 | exit(0); |
115 | } |
116 | return 0; |
117 | } |
118 | |
119 | |
120 | static int parse_args(int argc, char **argv) |
121 | { |
122 | int ho_error; |
123 | |
124 | if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) |
125 | exit(ho_error); |
126 | |
127 | /* |
128 | The following code is to make the command compatible with the old |
129 | version that required one to use the -n and -s options |
130 | */ |
131 | |
132 | if (argc == 2) |
133 | { |
134 | sym_fname= argv[0]; |
135 | dump_fname= argv[1]; |
136 | } |
137 | else if (argc == 1) |
138 | { |
139 | if (!sym_fname) |
140 | sym_fname = argv[0]; |
141 | else if (!dump_fname) |
142 | dump_fname = argv[0]; |
143 | else |
144 | { |
145 | usage(); |
146 | exit(1); |
147 | } |
148 | } |
149 | else if (argc != 0 || !sym_fname) |
150 | { |
151 | usage(); |
152 | exit(1); |
153 | } |
154 | return 0; |
155 | } |
156 | |
157 | |
158 | static void open_files() |
159 | { |
160 | fp_out = stdout; |
161 | fp_dump = stdin; |
162 | |
163 | if (dump_fname && !(fp_dump = my_fopen(dump_fname, O_RDONLY, MYF(MY_WME)))) |
164 | die("Could not open %s" , dump_fname); |
165 | /* if name not given, assume stdin*/ |
166 | |
167 | if (!sym_fname) |
168 | die("Please run nm --numeric-sort on mysqld binary that produced stack \ |
169 | trace dump and specify the path to it with -s or --symbols-file" ); |
170 | if (!(fp_sym = my_fopen(sym_fname, O_RDONLY, MYF(MY_WME)))) |
171 | die("Could not open %s" , sym_fname); |
172 | |
173 | } |
174 | |
175 | static uchar hex_val(char c) |
176 | { |
177 | uchar l; |
178 | if (my_isdigit(&my_charset_latin1,c)) |
179 | return c - '0'; |
180 | l = my_tolower(&my_charset_latin1,c); |
181 | if (l < 'a' || l > 'f') |
182 | return HEX_INVALID; |
183 | return (uchar)10 + ((uchar)c - (uchar)'a'); |
184 | } |
185 | |
186 | static my_long_addr_t read_addr(char** buf) |
187 | { |
188 | uchar c; |
189 | char* p = *buf; |
190 | my_long_addr_t addr = 0; |
191 | |
192 | while((c = hex_val(*p++)) != HEX_INVALID) |
193 | addr = (addr << 4) + c; |
194 | |
195 | *buf= p-1; |
196 | return addr; |
197 | } |
198 | |
199 | static int init_sym_entry(SYM_ENTRY* se, char* buf) |
200 | { |
201 | char* p, *p_end; |
202 | se->addr = (uchar*)read_addr(&buf); |
203 | |
204 | if (!se->addr) |
205 | return -1; |
206 | buf++; |
207 | while (my_isspace(&my_charset_latin1,*buf++)) |
208 | /* empty */; |
209 | |
210 | while (my_isspace(&my_charset_latin1,*buf++)) |
211 | /* empty - skip more space */; |
212 | --buf; |
213 | /* now we are on the symbol */ |
214 | for (p = se->symbol, p_end = se->symbol + sizeof(se->symbol) - 1; |
215 | *buf != '\n' && *buf && p < p_end; ++buf,++p) |
216 | *p = *buf; |
217 | *p = 0; |
218 | if (!strcmp(se->symbol, "gcc2_compiled." )) |
219 | return -1; |
220 | return 0; |
221 | } |
222 | |
223 | static void init_sym_table() |
224 | { |
225 | char buf[512]; |
226 | if (my_init_dynamic_array(&sym_table, sizeof(SYM_ENTRY), INIT_SYM_TABLE, |
227 | INC_SYM_TABLE, MYF(0))) |
228 | die("Failed in my_init_dynamic_array() -- looks like out of memory problem" ); |
229 | |
230 | while (fgets(buf, sizeof(buf), fp_sym)) |
231 | { |
232 | SYM_ENTRY se; |
233 | if (init_sym_entry(&se, buf)) |
234 | continue; |
235 | if (insert_dynamic(&sym_table, (uchar*)&se)) |
236 | die("insert_dynamic() failed - looks like we are out of memory" ); |
237 | } |
238 | |
239 | verify_sort(); |
240 | } |
241 | |
242 | static void clean_up() |
243 | { |
244 | delete_dynamic(&sym_table); |
245 | } |
246 | |
247 | static void verify_sort() |
248 | { |
249 | uint i; |
250 | uchar* last = 0; |
251 | |
252 | for (i = 0; i < sym_table.elements; i++) |
253 | { |
254 | SYM_ENTRY se; |
255 | get_dynamic(&sym_table, (uchar*)&se, i); |
256 | if (se.addr < last) |
257 | die("sym table does not appear to be sorted, did you forget \ |
258 | --numeric-sort arg to nm? trouble addr = %p, last = %p" , se.addr, last); |
259 | last = se.addr; |
260 | } |
261 | } |
262 | |
263 | |
264 | static SYM_ENTRY* resolve_addr(uchar* addr, SYM_ENTRY* se) |
265 | { |
266 | uint i; |
267 | get_dynamic(&sym_table, (uchar*)se, 0); |
268 | if (addr < se->addr) |
269 | return 0; |
270 | |
271 | for (i = 1; i < sym_table.elements; i++) |
272 | { |
273 | get_dynamic(&sym_table, (uchar*)se, i); |
274 | if (addr < se->addr) |
275 | { |
276 | get_dynamic(&sym_table, (uchar*)se, i - 1); |
277 | return se; |
278 | } |
279 | } |
280 | |
281 | return se; |
282 | } |
283 | |
284 | |
285 | /* |
286 | Resolve anything that starts with [0x or (+0x or start of line and 0x |
287 | Skip '_end' as this is an indication of a wrong symbol (stack?) |
288 | */ |
289 | |
290 | static void do_resolve() |
291 | { |
292 | char buf[1024], *p; |
293 | while (fgets(buf, sizeof(buf), fp_dump)) |
294 | { |
295 | for (p= buf ; *p ; p++) |
296 | { |
297 | int found= 0; |
298 | if (p[0] == '[' && p[1] == '0' && p[2] == 'x') |
299 | found= 3; |
300 | if (p[0] == '(' && p[1] == '+' && p[2] == '0' && p[3] == 'x') |
301 | found= 4; |
302 | |
303 | /* For stdin */ |
304 | if (p == buf && p[0] == '0' && p[1] == 'x') |
305 | found= 2; |
306 | |
307 | if (found) |
308 | { |
309 | SYM_ENTRY se ; |
310 | uchar *addr; |
311 | char *tmp= p + found; |
312 | addr= (uchar*)read_addr(&tmp); |
313 | if (resolve_addr(addr, &se) && strcmp(se.symbol, "_end" )) |
314 | { |
315 | fprintf(fp_out, "%c%p %s + %d" , *p, addr, se.symbol, |
316 | (int) (addr - se.addr)); |
317 | p= tmp-1; |
318 | } |
319 | else |
320 | { |
321 | fputc(*p, stdout); |
322 | } |
323 | } |
324 | else |
325 | fputc(*p, stdout); |
326 | } |
327 | } |
328 | } |
329 | |
330 | |
331 | int main(int argc, char** argv) |
332 | { |
333 | MY_INIT(argv[0]); |
334 | parse_args(argc, argv); |
335 | open_files(); |
336 | init_sym_table(); |
337 | do_resolve(); |
338 | clean_up(); |
339 | return 0; |
340 | } |
341 | |