unmangler 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1025 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path("base", File.dirname(__FILE__))
3
+
4
+ # ported from wine's undname.c
5
+ #
6
+ # most of the comments are from undname.c
7
+
8
+ module Unmangler; end
9
+
10
+ class Unmangler::MSVC < Unmangler::Base
11
+
12
+ UNDNAME_COMPLETE = 0x0000
13
+ UNDNAME_NO_LEADING_UNDERSCORES = 0x0001 # Don't show __ in calling convention
14
+ UNDNAME_NO_MS_KEYWORDS = 0x0002 # Don't show calling convention at all
15
+ UNDNAME_NO_FUNCTION_RETURNS = 0x0004 # Don't show function/method return value
16
+ UNDNAME_NO_ALLOCATION_MODEL = 0x0008
17
+ UNDNAME_NO_ALLOCATION_LANGUAGE = 0x0010
18
+ UNDNAME_NO_MS_THISTYPE = 0x0020
19
+ UNDNAME_NO_CV_THISTYPE = 0x0040
20
+ UNDNAME_NO_THISTYPE = 0x0060
21
+ UNDNAME_NO_ACCESS_SPECIFIERS = 0x0080 # Don't show access specifier = public/protected/private
22
+ UNDNAME_NO_THROW_SIGNATURES = 0x0100
23
+ UNDNAME_NO_MEMBER_TYPE = 0x0200 # Don't show static/virtual specifier
24
+ UNDNAME_NO_RETURN_UDT_MODEL = 0x0400
25
+ UNDNAME_32_BIT_DECODE = 0x0800
26
+ UNDNAME_NAME_ONLY = 0x1000 # Only report the variable/method name
27
+ UNDNAME_NO_ARGUMENTS = 0x2000 # Don't show method arguments
28
+ UNDNAME_NO_SPECIAL_SYMS = 0x4000
29
+ UNDNAME_NO_COMPLEX_TYPE = 0x8000
30
+
31
+ class ParsedSymbol < Struct.new(
32
+ :flags, # (unsigned int) the UNDNAME_ flags used for demangling
33
+ :current, # (const char*) pointer in input (mangled) string
34
+ :result, # (char*) demangled string
35
+ :names, # (struct array) array of names for back reference
36
+ :stack, # (struct array) stack of parsed strings
37
+ :alloc_list, # (void*) linked list of allocated blocks
38
+ :avail_in_first # (usigned int) number of available bytes in head block
39
+ )
40
+ def initialize *args
41
+ super
42
+ self.names ||= []
43
+ self.stack ||= []
44
+ end
45
+ end
46
+
47
+ DataType = Struct.new :left, :right # char*
48
+
49
+ def unmangle mangled, flags = UNDNAME_COMPLETE
50
+
51
+ if flags.is_a?(Hash)
52
+ flags = flags.fetch(:args, true) ? UNDNAME_COMPLETE : UNDNAME_NAME_ONLY
53
+ end
54
+
55
+ if flags & UNDNAME_NAME_ONLY != 0
56
+ flags |= UNDNAME_NO_FUNCTION_RETURNS | UNDNAME_NO_ACCESS_SPECIFIERS |
57
+ UNDNAME_NO_MEMBER_TYPE | UNDNAME_NO_ALLOCATION_LANGUAGE | UNDNAME_NO_COMPLEX_TYPE
58
+ end
59
+
60
+ sym = ParsedSymbol.new
61
+ sym.flags = flags
62
+ sym.current = StringPtr.new(mangled)
63
+
64
+ symbol_demangle(sym) ? sym.result.strip : mangled
65
+ end
66
+
67
+ private
68
+
69
+ # def warn fmt, *args
70
+ # STDERR.printf(fmt, *args)
71
+ # end
72
+
73
+ def err fmt, *args
74
+ if @debug
75
+ begin
76
+ STDERR.printf(fmt, *args)
77
+ rescue
78
+ STDERR.puts "[!] #{fmt.strip.inspect}, #{args.inspect}"
79
+ end
80
+ end
81
+ end
82
+
83
+ alias :warn :err
84
+
85
+ def symbol_demangle sym
86
+ ret = false
87
+ do_after = 0
88
+ dashed_null = "--null--"
89
+
90
+ #printf "[d] in: %s\n", sym.current[0..-1]
91
+
92
+ catch(:done) do
93
+ # seems wrong as name, as it demangles a simple data type
94
+ if sym.flags & UNDNAME_NO_ARGUMENTS != 0
95
+ ct = DataType.new
96
+ if (demangle_datatype(sym, ct, nil, false))
97
+ sym.result = sprintf("%s%s", ct.left, ct.right);
98
+ ret = true
99
+ end
100
+ throw :done
101
+ end # if
102
+
103
+ # MS mangled names always begin with '?'
104
+ return false unless sym.current[0] == '?'
105
+ sym.current.inc!
106
+
107
+ # Then function name or operator code
108
+ if (sym.current[0] == '?' && (sym.current[1] != '$' || sym.current[2] == '?'))
109
+ function_name = nil
110
+
111
+ if (sym.current[1] == '$')
112
+ do_after = 6
113
+ sym.current += 2
114
+ end
115
+
116
+ # C++ operator code (one character, or two if the first is '_')
117
+ case sym.current.inc_get!
118
+ when '0'; do_after = 1
119
+ when '1'; do_after = 2
120
+ when '2'; function_name = "operator new"
121
+ when '3'; function_name = "operator delete"
122
+ when '4'; function_name = "operator="
123
+ when '5'; function_name = "operator>>"
124
+ when '6'; function_name = "operator<<"
125
+ when '7'; function_name = "operator!"
126
+ when '8'; function_name = "operator=="
127
+ when '9'; function_name = "operator!="
128
+ when 'A'; function_name = "operator[]"
129
+ when 'B'; function_name = "operator "; do_after = 3
130
+ when 'C'; function_name = "operator."
131
+ when 'D'; function_name = "operator*"
132
+ when 'E'; function_name = "operator++"
133
+ when 'F'; function_name = "operator--"
134
+ when 'G'; function_name = "operator-"
135
+ when 'H'; function_name = "operator+"
136
+ when 'I'; function_name = "operator&"
137
+ when 'J'; function_name = "operator.*"
138
+ when 'K'; function_name = "operator/"
139
+ when 'L'; function_name = "operator%"
140
+ when 'M'; function_name = "operator<"
141
+ when 'N'; function_name = "operator<="
142
+ when 'O'; function_name = "operator>"
143
+ when 'P'; function_name = "operator>="
144
+ when 'Q'; function_name = "operator,"
145
+ when 'R'; function_name = "operator()"
146
+ when 'S'; function_name = "operator~"
147
+ when 'T'; function_name = "operator^"
148
+ when 'U'; function_name = "operator|"
149
+ when 'V'; function_name = "operator&&"
150
+ when 'W'; function_name = "operator||"
151
+ when 'X'; function_name = "operator*="
152
+ when 'Y'; function_name = "operator+="
153
+ when 'Z'; function_name = "operator-="
154
+ when '_'
155
+ case sym.current.inc_get!
156
+ when '0'; function_name = "operator/="
157
+ when '1'; function_name = "operator%="
158
+ when '2'; function_name = "operator>>="
159
+ when '3'; function_name = "operator<<="
160
+ when '4'; function_name = "operator&="
161
+ when '5'; function_name = "operator|="
162
+ when '6'; function_name = "operator^="
163
+ when '7'; function_name = "`vftable'"
164
+ when '8'; function_name = "`vbtable'"
165
+ when '9'; function_name = "`vcall'"
166
+ when 'A'; function_name = "`typeof'"
167
+ when 'B'; function_name = "`local static guard'"
168
+ when 'C'; function_name = "`string'"; do_after = 4
169
+ when 'D'; function_name = "`vbase destructor'"
170
+ when 'E'; function_name = "`vector deleting destructor'"
171
+ when 'F'; function_name = "`default constructor closure'"
172
+ when 'G'; function_name = "`scalar deleting destructor'"
173
+ when 'H'; function_name = "`vector constructor iterator'"
174
+ when 'I'; function_name = "`vector destructor iterator'"
175
+ when 'J'; function_name = "`vector vbase constructor iterator'"
176
+ when 'K'; function_name = "`virtual displacement map'"
177
+ when 'L'; function_name = "`eh vector constructor iterator'"
178
+ when 'M'; function_name = "`eh vector destructor iterator'"
179
+ when 'N'; function_name = "`eh vector vbase constructor iterator'"
180
+ when 'O'; function_name = "`copy constructor closure'"
181
+ when 'R'
182
+ sym.flags |= UNDNAME_NO_FUNCTION_RETURNS
183
+ case sym.current.inc_get!
184
+ when '0'
185
+ ct = DataType.new
186
+ pmt = []
187
+ sym.current.inc!
188
+ demangle_datatype(sym, ct, pmt, false)
189
+ function_name = sprintf("%s%s `RTTI Type Descriptor'", ct.left, ct.right)
190
+ sym.current.dec!
191
+ when '1'
192
+ sym.current.inc!
193
+ n1 = get_number(sym)
194
+ n2 = get_number(sym)
195
+ n3 = get_number(sym)
196
+ n4 = get_number(sym)
197
+ sym.current.dec!
198
+ function_name = sprintf("`RTTI Base Class Descriptor at (%s,%s,%s,%s)'", n1, n2, n3, n4)
199
+ when '2'; function_name = "`RTTI Base Class Array'"
200
+ when '3'; function_name = "`RTTI Class Hierarchy Descriptor'"
201
+ when '4'; function_name = "`RTTI Complete Object Locator'"
202
+ else
203
+ err("Unknown RTTI operator: _R%c\n", sym.current[0])
204
+ end # case sym.current.inc_get!
205
+ when 'S'; function_name = "`local vftable'"
206
+ when 'T'; function_name = "`local vftable constructor closure'"
207
+ when 'U'; function_name = "operator new[]"
208
+ when 'V'; function_name = "operator delete[]"
209
+ when 'X'; function_name = "`placement delete closure'"
210
+ when 'Y'; function_name = "`placement delete[] closure'"
211
+ else
212
+ err("Unknown operator: _%c\n", sym.current[0])
213
+ return false
214
+ end # case sym.current.inc_get!
215
+ else
216
+ # FIXME: Other operators
217
+ err("Unknown operator: %c\n", sym.current[0])
218
+ return false
219
+ end # case sym.current.inc_get!
220
+
221
+ sym.current.inc!
222
+
223
+ case do_after
224
+ when 1,2
225
+ sym.stack << dashed_null
226
+ when 4
227
+ sym.result = function_name
228
+ ret = true
229
+ throw :done
230
+ else
231
+ if do_after == 6
232
+ array_pmt = []
233
+ if args = get_args(sym, array_pmt, false, '<', '>')
234
+ function_name << args
235
+ end
236
+ sym.names = []
237
+ end
238
+ sym.stack << function_name
239
+ end # case do_after
240
+
241
+ elsif sym.current[0] == '$'
242
+ # Strange construct, it's a name with a template argument list and that's all.
243
+ sym.current.inc!
244
+ ret = (sym.result = get_template_name(sym)) != nil
245
+ throw :done
246
+ elsif sym.current[0,2] == '?$'
247
+ do_after = 5
248
+ end
249
+
250
+ # Either a class name, or '@' if the symbol is not a class member
251
+ case sym.current[0]
252
+ when '@'; sym.current.inc!
253
+ when '$'; # do nothing
254
+ else
255
+ # Class the function is associated with, terminated by '@@'
256
+ throw :done unless get_class(sym)
257
+ end # case sym.current[0]
258
+
259
+ case do_after
260
+ when 1,2
261
+ # it's time to set the member name for ctor & dtor
262
+ throw :done if sym.stack.size <= 1 # ZZZ may be wrong
263
+ if do_after == 1
264
+ sym.stack[0] = sym.stack[1]
265
+ else
266
+ sym.stack[0] = "~" + sym.stack[1]
267
+ end
268
+ # ctors and dtors don't have return type
269
+ sym.flags |= UNDNAME_NO_FUNCTION_RETURNS
270
+ when 3
271
+ sym.flags &= ~UNDNAME_NO_FUNCTION_RETURNS
272
+ when 5
273
+ sym.names.shift
274
+ end # case do_after
275
+
276
+ # Function/Data type and access level
277
+ ret =
278
+ case sym.current[0]
279
+ when /\d/
280
+ handle_data(sym)
281
+ when /[A-Z]/
282
+ handle_method(sym, do_after == 3)
283
+ when '$'
284
+ handle_template(sym)
285
+ else
286
+ false
287
+ end
288
+ end # catch(:done)
289
+
290
+ if ret
291
+ assert(sym.result)
292
+ else
293
+ warn("Failed at %s\n", sym.current[0..-1])
294
+ end
295
+
296
+ #printf "[d] out: %s\n", sym.result
297
+
298
+ ret
299
+ end
300
+
301
+ # Attempt to demangle a C++ data type, which may be datatype.
302
+ # a datatype type is made up of a number of simple types. e.g:
303
+ # char** = (pointer to (pointer to (char)))
304
+
305
+ def demangle_datatype(sym, ct, pmt_ref=nil, in_args=false)
306
+ dt = nil
307
+ add_pmt = true
308
+
309
+ assert(ct)
310
+ ct.left = ct.right = nil
311
+
312
+ catch :done do
313
+ case (dt = sym.current.get_inc!) #.tap{ |x| puts "[d] #{x}" }
314
+ when '_'
315
+ # MS type: __int8,__int16 etc
316
+ ct.left = get_extended_type(sym.current.get_inc!)
317
+
318
+ when *SIMPLE_TYPES.keys
319
+ # Simple data types
320
+ ct.left = get_simple_type(dt)
321
+ add_pmt = false
322
+
323
+ when 'T','U','V','Y'
324
+ # union, struct, class, cointerface
325
+ struct_name = type_name = nil
326
+
327
+ throw :done unless struct_name = get_class_name(sym)
328
+
329
+ if (sym.flags & UNDNAME_NO_COMPLEX_TYPE == 0)
330
+ case (dt)
331
+ when 'T'; type_name = "union "
332
+ when 'U'; type_name = "struct "
333
+ when 'V'; type_name = "class "
334
+ when 'Y'; type_name = "cointerface "
335
+ end
336
+ end
337
+ ct.left = sprintf("%s%s", type_name, struct_name)
338
+
339
+ when '?'
340
+ # not all the time is seems
341
+ if in_args
342
+ throw :done unless ptr = get_number(sym)
343
+ ct.left = "`template-parameter-#{ptr}'"
344
+ else
345
+ throw :done unless get_modified_type(ct, sym, pmt_ref, '?', in_args)
346
+ end
347
+
348
+ when 'A','B' # reference, volatile reference
349
+ throw :done unless get_modified_type(ct, sym, pmt_ref, dt, in_args)
350
+
351
+ when 'Q','R','S' # const pointer, volatile pointer, const volatile pointer
352
+ throw :done unless get_modified_type(ct, sym, pmt_ref, in_args ? dt : 'P', in_args)
353
+
354
+ when 'P' # Pointer
355
+ if isdigit(sym.current[0])
356
+ # FIXME: P6 = Function pointer, others who knows..
357
+ if (sym.current.get_inc! == '6')
358
+ call_conv = StringPtr.new
359
+ exported = StringPtr.new
360
+ sub_ct = DataType.new
361
+ saved_stack = sym.stack.dup
362
+
363
+ throw :done unless cc=get_calling_convention(
364
+ sym.current.get_inc!, sym.flags & ~UNDNAME_NO_ALLOCATION_LANGUAGE
365
+ )
366
+ call_conv, exported = cc
367
+
368
+ throw :done unless demangle_datatype(sym, sub_ct, pmt_ref, false)
369
+ throw :done unless args = get_args(sym, pmt_ref, true, '(', ')')
370
+ sym.stack = saved_stack
371
+
372
+ ct.left = sprintf("%s%s (%s*", sub_ct.left, sub_ct.right, call_conv)
373
+ ct.right = sprintf(")%s", args)
374
+ else
375
+ throw :done
376
+ end
377
+ else
378
+ throw :done unless get_modified_type(ct, sym, pmt_ref, 'P', in_args)
379
+ end
380
+
381
+ when 'W'
382
+ if (sym.current[0] == '4')
383
+ sym.current.inc!
384
+ throw :done unless enum_name = get_class_name(sym)
385
+ if sym.flags & UNDNAME_NO_COMPLEX_TYPE != 0
386
+ ct.left = enum_name
387
+ else
388
+ ct.left = sprintf("enum %s", enum_name)
389
+ end
390
+ else
391
+ throw :done
392
+ end
393
+
394
+ when /\d/
395
+ # Referring back to previously parsed type
396
+ # left and right are pushed as two separate strings
397
+ ct.left = pmt_ref[dt.to_i*2]
398
+ ct.right = pmt_ref[dt.to_i*2 + 1]
399
+ throw :done unless ct.left
400
+ add_pmt = false
401
+
402
+ when '$'
403
+ case sym.current.get_inc!
404
+ when '0'
405
+ throw :done unless ct.left = get_number(sym)
406
+ when 'D'
407
+ throw :done unless ptr = get_number(sym)
408
+ ct.left = sprintf("`template-parameter%s'", ptr)
409
+ when 'F'
410
+ throw :done unless p1 = get_number(sym)
411
+ throw :done unless p2 = get_number(sym)
412
+ ct.left = sprintf("{%s,%s}", p1, p2)
413
+ when 'G'
414
+ throw :done unless p1 = get_number(sym)
415
+ throw :done unless p2 = get_number(sym)
416
+ throw :done unless p3 = get_number(sym)
417
+ ct.left = sprintf("{%s,%s,%s}", p1, p2, p3)
418
+ when 'Q'
419
+ throw :done unless ptr = get_number(sym)
420
+ ct.left = sprintf("`non-type-template-parameter%s'", ptr)
421
+ when '$'
422
+ if (sym.current[0] == 'C')
423
+ ptr = ''
424
+ ptr_modif = ''
425
+ sym.current.inc!
426
+ throw :done unless get_modifier(sym, ptr, ptr_modif)
427
+ ptr = nil if ptr.empty?
428
+ ptr_modif = nil if ptr_modif.empty?
429
+
430
+ throw :done unless demangle_datatype(sym, ct, pmt_ref, in_args)
431
+ ct.left = sprintf("%s %s", ct.left, ptr)
432
+ end
433
+ end # case
434
+ else
435
+ err("Unknown type %c\n", dt)
436
+ end # case dt=...
437
+
438
+ if (add_pmt && pmt_ref && in_args)
439
+ # left and right are pushed as two separate strings
440
+ pmt_ref << (ct.left || "")
441
+ pmt_ref << (ct.right || "")
442
+ end # if
443
+
444
+ end # catch :done
445
+
446
+ return ct.left != nil
447
+ end # def demangle_datatype
448
+
449
+ def get_modified_type(ct, sym, pmt_ref, modif, in_args)
450
+ ptr_modif = ''
451
+
452
+ if sym.current[0] == 'E'
453
+ ptr_modif = " __ptr64"
454
+ sym.current.inc!
455
+ end
456
+
457
+ str_modif =
458
+ case modif
459
+ when 'A'; sprintf(" &%s", ptr_modif)
460
+ when 'B'; sprintf(" &%s volatile", ptr_modif)
461
+ when 'P'; sprintf(" *%s", ptr_modif)
462
+ when 'Q'; sprintf(" *%s const", ptr_modif)
463
+ when 'R'; sprintf(" *%s volatile", ptr_modif)
464
+ when 'S'; sprintf(" *%s const volatile", ptr_modif)
465
+ when '?'; ""
466
+ else
467
+ return false
468
+ end
469
+
470
+ modifier = ''
471
+ if get_modifier(sym, modifier, ptr_modif)
472
+ modifier = nil if modifier.empty?
473
+ ptr_modif = nil if ptr_modif.empty?
474
+
475
+ saved_stack = sym.stack.dup
476
+ sub_ct = DataType.new
477
+
478
+ # multidimensional arrays
479
+ if (sym.current[0] == 'Y')
480
+ sym.current.inc!
481
+ return false unless n1 = get_number(sym)
482
+ num = n1.to_i
483
+
484
+ if (str_modif[0] == ' ' && !modifier)
485
+ str_modif = str_modif[1..-1]
486
+ end
487
+
488
+ if (modifier)
489
+ str_modif = sprintf(" (%s%s)", modifier, str_modif)
490
+ modifier = nil
491
+ else
492
+ str_modif = sprintf(" (%s)", str_modif)
493
+ end
494
+
495
+ num.times do
496
+ str_modif = sprintf("%s[%s]", str_modif, get_number(sym))
497
+ end
498
+ end
499
+
500
+ # Recurse to get the referred-to type
501
+ return false unless demangle_datatype(sym, sub_ct, pmt_ref, false)
502
+
503
+ if modifier
504
+ ct.left = sprintf("%s %s%s", sub_ct.left, modifier, str_modif)
505
+ else
506
+ # don't insert a space between duplicate '*'
507
+ if (!in_args && str_modif[0] && str_modif[1] == '*' && sub_ct.left[-1] == '*')
508
+ str_modif = str_modif[1..-1]
509
+ end
510
+ ct.left = sprintf("%s%s", sub_ct.left, str_modif )
511
+ end
512
+ ct.right = sub_ct.right
513
+ sym.stack = saved_stack
514
+ end # if get_modifier
515
+ true
516
+ end # def get_modified_type
517
+
518
+ # Parses the type modifier.
519
+ # XXX ZZZ FIXME must check that ret & ptr_modif are not simple checked like
520
+ # if(!ret) or if(!ptr_modif)
521
+ def get_modifier(sym, ret, ptr_modif)
522
+ raise "ret must be a String" unless ret.is_a?(String)
523
+ raise "ptr_modif must be a String" unless ptr_modif.is_a?(String)
524
+
525
+ if sym.current[0] == 'E'
526
+ ptr_modif[0..-1] = "__ptr64"
527
+ sym.current.inc!
528
+ else
529
+ ptr_modif[0..-1] = ''
530
+ end
531
+
532
+ case sym.current.get_inc!
533
+ when 'A'; ret[0..-1] = '' # XXX original: *ret = NULL, may affect further checks
534
+ when 'B'; ret[0..-1] = "const"
535
+ when 'C'; ret[0..-1] = "volatile"
536
+ when 'D'; ret[0..-1] = "const volatile"
537
+ else
538
+ return false
539
+ end
540
+
541
+ true
542
+ end # def get_modifier
543
+
544
+ SIMPLE_TYPES = {
545
+ 'C' => "signed char", 'D' => "char", 'E' => "unsigned char",
546
+ 'F' => "short", 'G' => "unsigned short", 'H' => "int",
547
+ 'I' => "unsigned int", 'J' => "long", 'K' => "unsigned long",
548
+ 'M' => "float", 'N' => "double", 'O' => "long double",
549
+ 'X' => "void", 'Z' => "..."
550
+ }
551
+
552
+ EXTENDED_TYPES = {
553
+ 'D' => "__int8", 'E' => "unsigned __int8",
554
+ 'F' => "__int16", 'G' => "unsigned __int16",
555
+ 'H' => "__int32", 'I' => "unsigned __int32",
556
+ 'J' => "__int64", 'K' => "unsigned __int64",
557
+ 'L' => "__int128",'M' => "unsigned __int128",
558
+ 'N' => "bool", 'W' => "wchar_t"
559
+ }
560
+
561
+ def get_simple_type c; SIMPLE_TYPES[c]; end
562
+ def get_extended_type c; EXTENDED_TYPES[c]; end
563
+
564
+ # Parses class as a list of parent-classes, terminated by '@' and stores the
565
+ # result in 'a' array. Each parent-classes, as well as the inner element
566
+ # (either field/method name or class name), are represented in the mangled
567
+ # name by a literal name ([a-zA-Z0-9_]+ terminated by '@') or a back reference
568
+ # ([0-9]) or a name with template arguments ('?$' literal name followed by the
569
+ # template argument list). The class name components appear in the reverse
570
+ # order in the mangled name, e.g aaa@bbb@ccc@@ will be demangled to
571
+ # ccc::bbb::aaa
572
+ def get_class(sym)
573
+ name = nil
574
+ while sym.current[0] != '@'
575
+ case sym.current[0]
576
+ when "\0",'',nil; return false
577
+ when /\d/;
578
+ # numbered backreference
579
+ name = sym.names[sym.current.get_inc!.to_i]
580
+ when '?'
581
+ case sym.current.inc_get!
582
+ when '$'
583
+ sym.current.inc!
584
+ return false unless name = get_template_name(sym)
585
+ sym.names << name
586
+ when '?'
587
+ saved_stack, saved_names = sym.stack.dup, sym.names.dup
588
+ sym.stack = []
589
+ name = "`#{sym.result}'" if symbol_demangle(sym)
590
+ sym.stack, sym.names = saved_stack, saved_names
591
+ else
592
+ return false unless name = get_number(sym)
593
+ name = "`#{name}'"
594
+ end # case
595
+ else
596
+ name = get_literal_string(sym)
597
+ end # case
598
+ return false unless name
599
+ sym.stack << name
600
+ end # while
601
+ sym.current.inc!
602
+ true
603
+ end # def get_class
604
+
605
+ # Gets the literal name from the current position in the mangled symbol to the
606
+ # first '@' character. It pushes the parsed name to the symbol names stack and
607
+ # returns a pointer to it or nil in when of an error.
608
+ def get_literal_string(sym)
609
+ ptr = sym.current.dup
610
+ idx = sym.current.index(/[^A-Za-z0-9_$]/) || sym.current.strlen
611
+ if sym.current[idx] == '@'
612
+ sym.current += idx+1
613
+ sym.names << ptr[0, sym.current - ptr - 1]
614
+ return sym.names.last
615
+ else
616
+ err("Failed at '%c' in %s\n", sym.current[0..-1], idx)
617
+ return nil
618
+ end
619
+ end # def get_literal_string
620
+
621
+ # Does the final parsing and handling for a function or a method in a class.
622
+ def handle_method(sym, cast_op)
623
+ access = member_type = name = modifier = call_conv = exported = args_str = name = nil
624
+ array_pmt = []
625
+ ret = false
626
+ ct_ret = DataType.new
627
+
628
+ accmem = sym.current.get_inc!
629
+ return unless accmem =~ /[A-Z]/
630
+
631
+ if sym.flags & UNDNAME_NO_ACCESS_SPECIFIERS == 0
632
+ case ((accmem.ord - 'A'.ord) / 8)
633
+ when 0; access = "private: "
634
+ when 1; access = "protected: "
635
+ when 2; access = "public: "
636
+ end
637
+ end
638
+
639
+ if sym.flags & UNDNAME_NO_MEMBER_TYPE == 0
640
+ if accmem <= 'X'
641
+ case ((accmem.ord - 'A'.ord) % 8)
642
+ when 2,3; member_type = "static "
643
+ when 4,5; member_type = "virtual "
644
+ when 6,7; member_type = "virtual "; access = "[thunk]:#{access}"
645
+ end
646
+ end
647
+ end
648
+
649
+ name = get_class_string(sym, 0)
650
+
651
+ if ((accmem.ord - 'A'.ord) % 8 == 6 || (accmem.ord - '8'.ord) % 8 == 7) # a thunk
652
+ name = sprintf("%s`adjustor{%s}' ", name, get_number(sym))
653
+ end
654
+
655
+ if (accmem <= 'X')
656
+ if (((accmem.ord - 'A'.ord) % 8) != 2 && ((accmem.ord - 'A'.ord) % 8) != 3)
657
+ modifier = ''; ptr_modif = ''
658
+ # Implicit 'this' pointer
659
+ # If there is an implicit this pointer, const modifier follows
660
+ return unless get_modifier(sym, modifier, ptr_modif)
661
+ modifier = nil if modifier.empty?
662
+ ptr_modif = nil if ptr_modif.empty?
663
+ if (modifier || ptr_modif)
664
+ modifier = "#{modifier} #{ptr_modif}"
665
+ end
666
+ end
667
+ end
668
+
669
+ return unless cc=get_calling_convention(sym.current.get_inc!, sym.flags)
670
+ call_conv, exported = cc
671
+
672
+ # Return type, or @ if 'void'
673
+ if (sym.current[0] == '@')
674
+ ct_ret.left = "void"
675
+ ct_ret.right = nil
676
+ sym.current.inc!
677
+ else
678
+ return unless demangle_datatype(sym, ct_ret, array_pmt, false)
679
+ end
680
+
681
+ if sym.flags & UNDNAME_NO_FUNCTION_RETURNS != 0
682
+ ct_ret.left = ct_ret.right = nil
683
+ end
684
+
685
+ if cast_op
686
+ name = [name, ct_ret.left, ct_ret.right].join
687
+ ct_ret.left = ct_ret.right = nil
688
+ end
689
+
690
+ saved_stack = sym.stack.dup
691
+ return unless args_str = get_args(sym, array_pmt, true, '(', ')')
692
+ if sym.flags & UNDNAME_NAME_ONLY != 0
693
+ args_str = modifier = nil
694
+ end
695
+ sym.stack = saved_stack
696
+
697
+ # Note: '()' after 'Z' means 'throws', but we don't care here
698
+ # Yet!!! FIXME
699
+
700
+ sym.result = [
701
+ access, member_type, ct_ret.left, (ct_ret.left && !ct_ret.right) ? " " :
702
+ nil, call_conv, call_conv ? " " : nil, exported, name, args_str, modifier,
703
+ ct_ret.right
704
+ ].join
705
+
706
+ true
707
+ end # def handle_method
708
+
709
+ # From an array collected by get_class in sym.stack, constructs the
710
+ # corresponding string
711
+ def get_class_string(sym, start)
712
+ sym.stack[start..-1].reverse.join('::')
713
+ end
714
+
715
+ # Returns a static string corresponding to the calling convention described
716
+ # by char 'ch'. Sets export to true iff the calling convention is exported.
717
+
718
+ def get_calling_convention(ch, flags)
719
+ call_conv = exported = nil
720
+
721
+ unless ch
722
+ err("Unknown calling convention NULL\n")
723
+ return false
724
+ end
725
+
726
+ if (flags & (UNDNAME_NO_MS_KEYWORDS | UNDNAME_NO_ALLOCATION_LANGUAGE)) == 0
727
+ if (flags & UNDNAME_NO_LEADING_UNDERSCORES) != 0
728
+ exported = "dll_export " if (((ch.ord - 'A'.ord) % 2) == 1)
729
+ case ch
730
+ when 'A','B'; call_conv = "cdecl"
731
+ when 'C','D'; call_conv = "pascal"
732
+ when 'E','F'; call_conv = "thiscall"
733
+ when 'G','H'; call_conv = "stdcall"
734
+ when 'I','J'; call_conv = "fastcall"
735
+ when 'K','L'; # nothing
736
+ when 'M'; call_conv = "clrcall"
737
+ else
738
+ err("Unknown calling convention %c\n", ch)
739
+ return false
740
+ end
741
+ else
742
+ exported = "__dll_export " if (((ch.ord - 'A'.ord) % 2) == 1)
743
+ case ch
744
+ when 'A','B'; call_conv = "__cdecl"
745
+ when 'C','D'; call_conv = "__pascal"
746
+ when 'E','F'; call_conv = "__thiscall"
747
+ when 'G','H'; call_conv = "__stdcall"
748
+ when 'I','J'; call_conv = "__fastcall"
749
+ when 'K','L'; # nothing
750
+ when 'M'; call_conv = "__clrcall"
751
+ else
752
+ err("Unknown calling convention %c\n", ch)
753
+ return false
754
+ end
755
+ end
756
+ end
757
+
758
+ [call_conv, exported]
759
+ end # def get_calling_convention
760
+
761
+ # Parses a list of function/method arguments, creates a string corresponding
762
+ # to the arguments' list.
763
+
764
+ def get_args(sym, pmt_ref, z_term, open_char, close_char)
765
+ ct = DataType.new
766
+ arg_collect = []
767
+ args_str = last = nil
768
+
769
+ # Now come the function arguments
770
+ while sym.current[0]
771
+ # Decode each data type and append it to the argument list
772
+ if sym.current[0] == '@'
773
+ sym.current.inc!
774
+ break
775
+ end
776
+ return unless demangle_datatype(sym, ct, pmt_ref, true)
777
+
778
+ # 'void' terminates an argument list in a function
779
+ break if z_term && ct.left == "void"
780
+ arg_collect << [ct.left, ct.right].join
781
+ break if ct.left == "..."
782
+ end
783
+
784
+ # Functions are always terminated by 'Z'. If we made it this far and don't
785
+ # find it, we have incorrectly identified a data type.
786
+
787
+ return if z_term && sym.current.get_inc! != 'Z'
788
+
789
+ if arg_collect.empty? || arg_collect == ['void']
790
+ return [open_char, "void", close_char].join
791
+ end
792
+
793
+ args_str = arg_collect.join(', ')
794
+ # "...>>" => "...> >"
795
+ args_str << " " if close_char == '>' && args_str[-1] == '>'
796
+
797
+ [open_char, args_str, close_char].join
798
+ end # def get_args
799
+
800
+ # Wrapper around get_class and get_class_string.
801
+ def get_class_name sym
802
+ saved_stack = sym.stack.dup
803
+ s = nil
804
+
805
+ if get_class(sym)
806
+ s = get_class_string(sym, saved_stack.size) # ZZZ ???
807
+ end
808
+ sym.stack = saved_stack
809
+ s
810
+ end # def get_class_name
811
+
812
+ # Parses a name with a template argument list and returns it as a string.
813
+ # In a template argument list the back reference to the names table is
814
+ # separately created. '0' points to the class component name with the
815
+ # template arguments. We use the same stack array to hold the names but
816
+ # save/restore the stack state before/after parsing the template argument
817
+ # list.
818
+ def get_template_name sym
819
+ name = args = nil
820
+ saved_names = sym.names.dup
821
+ saved_stack = sym.stack.dup
822
+ array_pmt = []
823
+
824
+ sym.names = []
825
+ return unless name = get_literal_string(sym)
826
+ name << args if args = get_args(sym, array_pmt, false, '<', '>')
827
+
828
+ sym.names = saved_names
829
+ sym.stack = saved_stack
830
+
831
+ name
832
+ end # def get_template_name
833
+
834
+ def get_number sym
835
+ ptr = nil
836
+ sgn = false
837
+
838
+ if (sym.current[0] == '?')
839
+ sgn = true
840
+ sym.current.inc!
841
+ end
842
+
843
+ case sym.current[0]
844
+ when /[0-8]/
845
+ ptr = " "
846
+ ptr[0] = '-' if sgn
847
+ ptr[sgn ? 1 : 0] = (sym.current[0].ord + 1).chr
848
+ sym.current.inc!
849
+ ptr.strip!
850
+ when '9'
851
+ ptr = " "
852
+ ptr[0] = '-' if sgn
853
+ ptr[sgn ? 1 : 0] = '1'
854
+ ptr[sgn ? 2 : 1] = '0'
855
+ sym.current.inc!
856
+ ptr.strip!
857
+ when /[A-P]/
858
+ ret = 0
859
+ while sym.current[0] =~ /[A-P]/
860
+ ret *= 16
861
+ ret += sym.current.get_inc!.ord - 'A'.ord
862
+ end
863
+ return nil unless sym.current[0] == '@'
864
+
865
+ ptr = sprintf("%s%d", sgn ? "-" : "", ret)
866
+ sym.current.inc!
867
+ else
868
+ return nil
869
+ end
870
+
871
+ ptr
872
+ end # def get_number
873
+
874
+ # Does the final parsing and handling for a name with templates
875
+
876
+ def handle_template sym
877
+ assert(sym.current[0] == '$')
878
+ sym.current.inc!
879
+ return false unless name = get_literal_string(sym)
880
+ return false unless args = get_args(sym, nil, false, '<', '>')
881
+ sym.result = [name, args].join
882
+ true
883
+ end # def handle_template
884
+
885
+ # Does the final parsing and handling for a variable or a field in a class.
886
+
887
+ def handle_data sym
888
+ name = access = member_type = modifier = ptr_modif = nil
889
+ ct = DataType.new
890
+ ret = false
891
+
892
+ # 0 private static
893
+ # 1 protected static
894
+ # 2 public static
895
+ # 3 private non-static
896
+ # 4 protected non-static
897
+ # 5 public non-static
898
+ # 6 ?? static
899
+ # 7 ?? static
900
+
901
+ if sym.flags & UNDNAME_NO_ACCESS_SPECIFIERS == 0
902
+ # we only print the access for static members
903
+ case sym.current[0]
904
+ when '0'; access = "private: "
905
+ when '1'; access = "protected: "
906
+ when '2'; access = "public: "
907
+ end
908
+ end
909
+
910
+ if sym.flags & UNDNAME_NO_MEMBER_TYPE == 0
911
+ member_type = "static " if sym.current[0] =~ /[012]/
912
+ end
913
+
914
+ name = get_class_string(sym, 0)
915
+
916
+ case sym.current.get_inc!
917
+ when /[0-5]/
918
+ saved_stack = sym.stack.dup
919
+ modifier = ''; ptr_modif = ''
920
+ pmt = []
921
+ return unless demangle_datatype(sym, ct, pmt, false)
922
+ return unless get_modifier(sym, modifier, ptr_modif)
923
+ modifier = nil if modifier.empty?
924
+ ptr_modif = nil if ptr_modif.empty?
925
+
926
+ if modifier && ptr_modif
927
+ modifier += " " + ptr_modif
928
+ elsif !modifier
929
+ modifier = ptr_modif
930
+ end
931
+ sym.stack = saved_stack
932
+
933
+ when '6','7' # compiler generated static
934
+ ct.left = ct.right = nil
935
+ modifier = ''; ptr_modif = ''
936
+ return unless get_modifier(sym, modifier, ptr_modif)
937
+ modifier = nil if modifier.empty?
938
+ ptr_modif = nil if ptr_modif.empty?
939
+
940
+ if (sym.current[0] != '@')
941
+ return unless cls = get_class_name(sym)
942
+ ct.right = "{for `#{cls}'}"
943
+ end
944
+ when '8','9'
945
+ modifier = ct.left = ct.right = nil
946
+ else
947
+ return
948
+ end # case
949
+
950
+ if (sym.flags & UNDNAME_NAME_ONLY != 0)
951
+ ct.left = ct.right = modifier = nil
952
+ end
953
+
954
+ sym.result = [
955
+ access, member_type, ct.left, modifier && ct.left ? " " : nil, modifier,
956
+ modifier || ct.left ? " " : nil, name, ct.right
957
+ ].join
958
+
959
+ true
960
+ end # def handle_data
961
+
962
+ end # class MSVC
963
+
964
+ ######################################################################
965
+
966
+ if $0 == __FILE__
967
+ $:.unshift("./lib")
968
+ require 'unmangler/string_ptr'
969
+ require 'awesome_print'
970
+ require 'pp'
971
+
972
+ def check src, want, flags = 0
973
+ want = src if want == :bad
974
+
975
+ u = Unmangler::MSVC.new
976
+ got = nil
977
+ begin
978
+ got = u.unmangle(src, flags)
979
+ rescue
980
+ pp u
981
+ raise
982
+ end
983
+ if got == want
984
+ print ".".green
985
+ else
986
+ puts
987
+ puts "[!] src: #{src.inspect.gray}"
988
+ puts "[!] want: #{want.inspect.yellow}"
989
+ puts "[!] got: #{got.inspect.red}"
990
+ # pp u
991
+ # exit 1
992
+ end
993
+ end
994
+
995
+ if ARGV.any?
996
+ check ARGV[0], ARGV[1]
997
+ exit
998
+ end
999
+
1000
+ check "?h@@YAXH@Z", "void __cdecl h(int)"
1001
+ check "?AFXSetTopLevelFrame@@YAXPAVCFrameWnd@@@Z", "void __cdecl AFXSetTopLevelFrame(class CFrameWnd *)"
1002
+ check "??0_Lockit@std@@QAE@XZ", "public: __thiscall std::_Lockit::_Lockit(void)"
1003
+
1004
+ check "?SetAt@CString@@QAEXHD@Z", "public: void __thiscall CString::SetAt(int, char)"
1005
+ check "?LoadFrame@CMDIFrameWndEx@@UAEHIKPAVCWnd@@PAUCCreateContext@@@Z",
1006
+ "public: virtual int __thiscall CMDIFrameWndEx::LoadFrame(unsigned int, unsigned long, class CWnd *, struct CCreateContext *)"
1007
+
1008
+ check "??0DNameStatusNode@@AEAA@W4DNameStatus@@@Z",
1009
+ "private: __cdecl DNameStatusNode::DNameStatusNode(enum DNameStatus) __ptr64"
1010
+
1011
+ check "?Add@?$CArray@VCSize@@V1@@@QAEHVCSize@@@Z",
1012
+ "public: int __thiscall CArray<class CSize, class CSize>::Add(class CSize)"
1013
+
1014
+ check "??$_Char_traits_cat@U?$char_traits@D@std@@@std@@YA?AU_Secure_char_traits_tag@0@XZ",
1015
+ "struct std::_Secure_char_traits_tag __cdecl std::_Char_traits_cat<struct std::char_traits<char> >(void)"
1016
+
1017
+ check "?dtor$0@?0???0CDockSite@@QEAA@XZ@4HA",
1018
+ "int `public: __cdecl CDockSite::CDockSite(void) __ptr64'::`1'::dtor$0"
1019
+
1020
+ # bad examples
1021
+ check "?ProcessAndDestroyEdit", :bad
1022
+ check "?dtor$0@?0??Add@?$CArray@VXQATItem@XQAT@CMFCRibbonInfo@@V123@@@QEA", :bad
1023
+
1024
+ puts
1025
+ end