unmangler 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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