unmangler 0.0.1

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,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTJiYThhZDM5Mzk5ZWVmNTJhNDgxNDEzYTVhYzhhOTU0MjNjOTQ1Ng==
5
+ data.tar.gz: !binary |-
6
+ MWFiNWRjNGZiODgxOGNkZWQyYmJmMmM4MjNhMGZhOWRkMTVjNzMwZQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NGIzMGJmNjg5NzlhOTVlMWNjOTkzZTJlNzdkODcyMDZhNmQwODlmNzkwZDlk
10
+ NmY0ZGE1MDdlYjY1MmUyZDk2YWNiZjkwMGJhZTZmZmVjM2M4ZjQ5MjhhYjQ5
11
+ NTVlYTRjZTc0NmMwMDc4ZjY3NWViNzUzMjhjZjRlODdlYzdlZjI=
12
+ data.tar.gz: !binary |-
13
+ YTY0ZWQ0ZDExZWUzMGRhZWVjYmFjNGY1ZmVkZjgyNmI3ZDhkMGQ0NDk2MGQ0
14
+ YTFiNTJiYTg3ZWM1NjI1Y2QyMWRhZTE2MWU2MmZiOTViN2FkNTg2YjZlMDA3
15
+ NjA0YTBlYTM3MTcyMGYzNjVhZDYyNDhhOTgyMWMzZDBkODEyMWU=
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in unmangler.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrey "Zed" Zaikin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,59 @@
1
+ # Unmangler
2
+
3
+ Unmangles mangled C++/Delphi names
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'unmangler'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install unmangler
18
+
19
+ ## Usage
20
+
21
+ ### Full unmangle #1
22
+ ```ruby
23
+ puts Unmangler.unmangle "@afunc$qxzcupi"
24
+
25
+ # output:
26
+ afunc(const signed char, int *)
27
+ ```
28
+
29
+ ### Name-only unmangle #1
30
+ ```ruby
31
+ puts Unmangler.unmangle "@afunc$qxzcupi", :args => false
32
+
33
+ # output:
34
+ afunc
35
+ ```
36
+
37
+ ### Full unmangle #2
38
+ ```ruby
39
+ puts Unmangler.unmangle "@Forms@TApplication@SetTitle$qqrx17System@AnsiString"
40
+
41
+ # output:
42
+ __fastcall Forms::TApplication::SetTitle(const System::AnsiString)
43
+ ```
44
+
45
+ ### Name-only unmangle #2
46
+ ```ruby
47
+ puts Unmangler.unmangle "@Forms@TApplication@SetTitle$qqrx17System@AnsiString", :args => false
48
+
49
+ # output:
50
+ Forms::TApplication::SetTitle
51
+ ```
52
+
53
+ ## Contributing
54
+
55
+ 1. Fork it
56
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
57
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
58
+ 4. Push to the branch (`git push origin my-new-feature`)
59
+ 5. Create new Pull Request
@@ -0,0 +1,47 @@
1
+ # Unmangler
2
+
3
+ Unmangles mangled C++/Delphi names
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'unmangler'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install unmangler
18
+
19
+ ## Usage
20
+
21
+ ### Full unmangle #1
22
+ ```ruby
23
+ puts Unmangler.unmangle "@afunc$qxzcupi"
24
+ ```
25
+
26
+ ### Name-only unmangle #1
27
+ ```ruby
28
+ puts Unmangler.unmangle "@afunc$qxzcupi", :args => false
29
+ ```
30
+
31
+ ### Full unmangle #2
32
+ ```ruby
33
+ puts Unmangler.unmangle "@Forms@TApplication@SetTitle$qqrx17System@AnsiString"
34
+ ```
35
+
36
+ ### Name-only unmangle #2
37
+ ```ruby
38
+ puts Unmangler.unmangle "@Forms@TApplication@SetTitle$qqrx17System@AnsiString", :args => false
39
+ ```
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create new Pull Request
@@ -0,0 +1,29 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc "run specs"
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
8
+
9
+ desc "build readme"
10
+ task :readme do
11
+ tpl = File.read('README.md.tpl')
12
+ result = tpl.gsub(/^### ([^~`\n]+?)\n```ruby(.+?)^```/m) do |x|
13
+ title, code = $1, $2
14
+
15
+ File.open("tmp.rb", "w:utf-8") do |f|
16
+ f.puts "require 'unmangler'"
17
+ f.puts code
18
+ end
19
+
20
+ puts "[.] #{title} .. "
21
+ out = `ruby -Ilib tmp.rb`
22
+ exit unless $?.success?
23
+
24
+ x.sub code, code+"\n # output:\n"+out.split("\n").map{|x| " #{x}"}.join("\n")+"\n"
25
+ end
26
+ File.unlink("tmp.rb") rescue nil
27
+ File.open('README.md','w'){ |f| f << result }
28
+ #puts result
29
+ end
@@ -0,0 +1,23 @@
1
+ require 'unmangler/version'
2
+ require 'unmangler/borland'
3
+
4
+ module Unmangler
5
+ class << self
6
+ def unmangle name, args={}
7
+ if name[0,1] == "@"
8
+ Unmangler::Borland.safe_unmangle name, args
9
+ # TODO: check if result is same as input
10
+ # and try to unmangle with MS if it is
11
+ elsif name[0,2] == '_Z'
12
+ # GCC ?
13
+ name
14
+ elsif name[0,1] == '?'
15
+ # MS ?
16
+ name
17
+ else
18
+ # return original name
19
+ name
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,1152 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'unmangler/string_ptr'
4
+
5
+ # ported from Embarcadero RAD Studio XE3
6
+ # $(BDS)\source\cpprtl\Source\misc\unmangle.c
7
+ #
8
+ # most of the comments are from unmangle.c
9
+
10
+ module Unmangler; end
11
+
12
+ class Unmangler::Borland
13
+ attr_accessor :kind
14
+
15
+ UM_UNKNOWN = 0x00000000
16
+
17
+ UM_FUNCTION = 0x00000001
18
+ UM_CONSTRUCTOR = 0x00000002
19
+ UM_DESTRUCTOR = 0x00000003
20
+ UM_OPERATOR = 0x00000004
21
+ UM_CONVERSION = 0x00000005
22
+ UM_DATA = 0x00000006
23
+ UM_THUNK = 0x00000007
24
+ UM_TPDSC = 0x00000008
25
+ UM_VTABLE = 0x00000009
26
+ UM_VRDF_THUNK = 0x0000000a
27
+ UM_DYN_THUNK = 0x0000000b
28
+
29
+ UM_KINDMASK = 0x000000ff
30
+
31
+ # Modifier (is it a member, template?).
32
+
33
+ UM_QUALIFIED = 0x00000100
34
+ UM_TEMPLATE = 0x00000200
35
+ UM_VIRDEF_FLAG = 0x00000400
36
+ UM_FRIEND_LIST = 0x00000800
37
+ UM_CTCH_HNDL_TBL = 0x00001000
38
+ UM_OBJ_DEST_TBL = 0x00002000
39
+ UM_THROW_LIST = 0x00004000
40
+ UM_EXC_CTXT_TBL = 0x00008000
41
+ UM_LINKER_PROC = 0x00010000
42
+ UM_SPECMASK = 0x0001fc00
43
+
44
+ UM_MODMASK = 0x00ffff00
45
+
46
+ # Some @kind of error occurred.
47
+
48
+ UM_BUFOVRFLW = 0x01000000
49
+ UM_HASHTRUNC = 0x02000000
50
+ UM_ERROR = 0x04000000
51
+
52
+ UM_ERRMASK = 0x7f000000
53
+
54
+ # This symbol is not a mangled name.
55
+
56
+ UM_NOT_MANGLED = 0x80000000
57
+
58
+ MAXBUFFLEN = 8192 # maximum output length
59
+
60
+ # The mangler, when mangling argument types, will create
61
+ # backreferences if the type has already been seen. These take the
62
+ # form t?, where ? can be either 0-9, or a-z.
63
+
64
+
65
+ # New mangle scheme lengths:
66
+ # len == 254 ==> old hash
67
+ # len == 253 ==> new MD5 hash
68
+ # len < 253 ==> unhashed
69
+
70
+ QUALIFIER = '@'
71
+ ARGLIST = '$'
72
+ TMPLCODE = '%'
73
+
74
+ def input
75
+ # if @srcindx >= @hashstart
76
+ # raise 'UM_HASHTRUNC'
77
+ # else
78
+ c = @source[0]
79
+ #c == "\x00" ? 0 : c
80
+ # end
81
+ end
82
+
83
+ def advance
84
+ @source.inc!
85
+ input
86
+ end
87
+
88
+ def copy_char c
89
+ @target[0] = (c == 0 ? "\x00" : c)
90
+ @target.inc!
91
+ end
92
+
93
+ def copy_string s, len=0
94
+ if len == 0
95
+ @target << s
96
+ else
97
+ @target << s[0,len]
98
+ end
99
+ end
100
+
101
+ alias :append :copy_string
102
+
103
+ # copy all remaining input until input end or any of term_chars is met
104
+ def copy_until *term_chars
105
+ term =
106
+ case term_chars.size
107
+ when 0; raise "no term_chars"
108
+ when 1; term_chars.first
109
+ else
110
+ Regexp.new( "[" + term_chars.map{ |c| Regexp::escape(c) }.join + "]" )
111
+ end
112
+ len = @source.index(term) || @source[0..-1].size
113
+ @target << @source[0,len]
114
+ @source += len
115
+ end
116
+
117
+ def strchr haystack, needle
118
+ if idx = haystack.index(needle)
119
+ StringPtr.new(haystack, idx)
120
+ else
121
+ nil
122
+ end
123
+ end
124
+
125
+ OPS = {
126
+ "add" => "+", "adr" => "&", "and" => "&", "asg" => "=", "land"=> "&&",
127
+ "lor" => "||", "call"=> "()", "cmp" => "~", "fnc" => "()", "dec" => "--",
128
+ "div" => "/", "eql" => "==", "geq" => ">=", "gtr" => ">", "inc" => "++",
129
+ "ind" => "*", "leq" => "<=", "lsh" => "<<", "lss" => "<", "mod" => "%",
130
+ "mul" => "*", "neq" => "!=", "new" => "new","not" => "!", "or" => "|",
131
+ "rand"=> "&=", "rdiv"=> "/=", "rlsh"=> "<<=","rmin"=> "-=", "rmod"=> "%=",
132
+ "rmul"=> "*=", "ror" => "|=", "rplu"=> "+=", "rrsh"=> ">>=","rsh" => ">>",
133
+ "rxor"=> "^=", "subs"=> "[]", "sub" => "-", "xor" => "^", "arow"=> "->",
134
+ "nwa" => "new[]", "dele"=> "delete", "dla" => "delete[]"
135
+ }
136
+
137
+ def copy_op src
138
+ copy_string(OPS[src] || src)
139
+ end
140
+
141
+ def copy_return_type(start, callconv, regconv, process_return)
142
+ start = start.dup if start.is_a?(StringPtr)
143
+
144
+ ret_len = 0
145
+ # Process the return type of a function, and shuffle the output
146
+ # text around so it looks like the return type came first.
147
+ ret_type = @target.dup
148
+
149
+ unless [0,nil,false].include?(process_return)
150
+ copy_type(@target, 0)
151
+ copy_char(' ')
152
+ end
153
+
154
+ copy_string(callconv) if callconv
155
+ copy_string(regconv) if regconv
156
+
157
+ ret_len = @target - ret_type
158
+
159
+ # Set up the return type to have a space after it.
160
+
161
+ # "foo((*)(float, int)double "
162
+
163
+ buff = ret_type[0, ret_len]
164
+ start[ret_len, ret_type-start] = start[0, ret_type-start]
165
+ start[0, ret_len] = buff[0, ret_len]
166
+
167
+ # "foo(double (*)(float, int)"
168
+
169
+ # If we are inserting this return type at the very beginning of a
170
+ # string, it means the location of all the qualifier names is
171
+ # about to move.
172
+
173
+ unless [0,nil,false].include?(@adjust_quals)
174
+ @namebase += ret_len if @namebase
175
+ @qualend += ret_len if @qualend
176
+ @prevqual += ret_len if @prevqual
177
+ @base_name+= ret_len if @base_name
178
+ @base_end += ret_len if @base_end
179
+ end
180
+ end # def copy_return_type
181
+
182
+ def copy_type(start, arglvl)
183
+ start = start.dup if start.is_a?(StringPtr)
184
+
185
+ tname = nil
186
+ c = input()
187
+ is_const = false
188
+ is_volatile = false
189
+ is_signed = false
190
+ is_unsigned = false
191
+ maxloop = 101
192
+ savedsavechar = nil
193
+
194
+ arglvl =
195
+ case arglvl
196
+ when 0, nil, false; false
197
+ else true
198
+ end
199
+
200
+ loop do
201
+ assert((maxloop-=1) > 0)
202
+ case c
203
+ when 'u'; is_unsigned = true
204
+ when 'z'; is_signed = true
205
+ when 'x'; is_const = true
206
+ when 'w'; is_volatile = true
207
+ when 'y'
208
+ # 'y' for closure is followed by 'f' or 'n'
209
+ c = advance()
210
+ assert(c == 'f' || c == 'n')
211
+ copy_string("__closure")
212
+ else
213
+ break
214
+ end
215
+
216
+ c = advance()
217
+ end # loop
218
+
219
+ if isdigit(c) # enum or class name
220
+ i = 0
221
+
222
+ begin # compute length
223
+ i = i * 10 + (c.ord - '0'.ord)
224
+ c = advance()
225
+ end while isdigit(c)
226
+
227
+ # Output whether this class name was const or volatile.
228
+
229
+ # These were already printed (see [BCB-265738])
230
+ #if 0
231
+ # if (is_const) copy_string("const ")
232
+ # if (is_volatile) copy_string("volatile ")
233
+ #endif
234
+
235
+ # ZZZ
236
+ s0 = @source.string.dup
237
+ @source[i] = "\x00"
238
+ @source.trim!
239
+ copy_name(0)
240
+ @source.string = s0
241
+ @target.trim!
242
+ return
243
+ end # if isdigit(c)
244
+
245
+ @savechar = c
246
+ tname = nil
247
+
248
+ if c == 'M' # member pointer
249
+ name = @target.dup
250
+ # We call 'copy_type' because it knows how to extract
251
+ # length-prefixed names.
252
+ advance()
253
+ copy_type(@target, 0)
254
+ len = @target - name
255
+ len = MAXBUFFLEN - 1 if (len > MAXBUFFLEN - 1)
256
+ strncpy(buff, name, len)
257
+ buff[len] = 0
258
+ @target = name
259
+ end
260
+
261
+ case c
262
+ when 'v'; tname = "void"
263
+ when 'c'; tname = "char"
264
+ when 'b'; tname = "wchar_t"
265
+ when 's'; tname = "short"
266
+ when 'i'; tname = "int"
267
+ when 'l'; tname = "long"
268
+ when 'f'; tname = "float"
269
+ when 'd'; tname = "double"
270
+ when 'g'; tname = "long double"
271
+ when 'j'; tname = "long long"
272
+ when 'o'; tname = "bool"
273
+ when 'e'; tname = "..."
274
+
275
+ when 'C' # C++ wide char
276
+ c = advance()
277
+ if (c == 's')
278
+ tname = "char16_t"
279
+ elsif (c == 'i')
280
+ tname = "char32_t"
281
+ else
282
+ raise "Unknown type"
283
+ end
284
+
285
+ when 'M','r','h','p' # member pointer, reference, rvalue reference, pointer
286
+ if (@savechar == 'M')
287
+ c = input() # [BTS-??????]
288
+ case c
289
+ when 'x'; is_const = true; c = advance() # [BCB-272500]
290
+ when 'w'; is_volatile = true; c = advance()
291
+ end
292
+ else
293
+ c = advance()
294
+ end
295
+
296
+ if (c == 'q') # function pointer
297
+ copy_char('(')
298
+
299
+ if (@savechar == 'M')
300
+ copy_string(buff)
301
+ append "::"
302
+ end
303
+
304
+ append "*)"
305
+
306
+ @savechar = c
307
+ end
308
+
309
+ savedsavechar = @savechar; # [BTS-263572]
310
+ copy_type(start, 0)
311
+ @savechar = savedsavechar
312
+
313
+ case @savechar
314
+ when 'r'; copy_char('&')
315
+ when 'h'; append('&&')
316
+ when 'p'; append(' *')
317
+ when 'M'
318
+ assert(buff[0])
319
+ copy_char(' ')
320
+ copy_string(buff)
321
+ append '::*'
322
+ end
323
+
324
+ when 'a' # array
325
+ dims = ''
326
+
327
+ begin
328
+ c = advance()
329
+ dims << '['
330
+ c = advance() if (c == '0') # 0 size means unspecified
331
+ while (c != '$') # collect size, up to '$'
332
+ dims << c
333
+ c = advance()
334
+ end
335
+ assert(c == '$')
336
+ c = advance()
337
+ dims << ']'
338
+ end while (c == 'a') # collect all dimensions
339
+
340
+ copy_type(@target, 0)
341
+ copy_string(dims)
342
+
343
+ when 'q' # function
344
+ callconv = regconv = hasret = save_adjqual = nil
345
+
346
+ # We want the return type first, but find it last. So we emit
347
+ # all but the return type, get the return type, then shuffle
348
+ # to get them in the right place.
349
+
350
+ loop do
351
+ break if (advance() != 'q')
352
+
353
+ case advance()
354
+ when 'c'; callconv = "__cdecl "
355
+ when 'p'; callconv = "__pascal "
356
+ when 'r'; callconv = "__fastcall "
357
+ when 'f'; callconv = "__fortran "
358
+ when 's'; callconv = "__stdcall "
359
+ when 'y'; callconv = "__syscall "
360
+ when 'i'; callconv = "__interrupt "
361
+ when 'g'; regconv = "__saveregs "
362
+ end
363
+ end
364
+
365
+ save_adjqual = @adjust_quals
366
+ @adjust_quals = 0
367
+
368
+ copy_char('(')
369
+ copy_args('$', 0)
370
+ copy_char(')')
371
+
372
+ @adjust_quals = save_adjqual
373
+
374
+ hasret = input() == '$'
375
+ advance() if hasret
376
+
377
+ if (hasret || callconv || regconv)
378
+ copy_return_type(start, callconv, regconv, hasret)
379
+ end
380
+
381
+ when ARGLIST # template arg list
382
+ # break
383
+ when TMPLCODE # template reference
384
+ # break
385
+
386
+ else
387
+ raise "Unknown type: #{c.inspect}"
388
+ end # case
389
+
390
+ if (tname)
391
+ copy_string("const ") if is_const
392
+ copy_string("volatile ") if is_volatile
393
+ copy_string("signed ") if is_signed
394
+ copy_string("unsigned ") if is_unsigned
395
+ copy_string(tname) if (!arglvl || @savechar != 'v')
396
+ advance()
397
+ else
398
+ copy_string(" const") if is_const
399
+ copy_string(" volatile") if is_volatile
400
+ end
401
+ end # def copy_type
402
+
403
+ def copy_delphi4args(_end, tmplargs)
404
+ first = true
405
+ _begin = start = nil
406
+ termchar = 0
407
+
408
+ tmplargs =
409
+ case tmplargs
410
+ when 0, nil, false; false
411
+ else true
412
+ end
413
+
414
+ c = input()
415
+ while (c && c != _end)
416
+ if first
417
+ first = false
418
+ else
419
+ append ', '
420
+ end
421
+
422
+ _begin = @source.dup
423
+ start = @target.dup
424
+
425
+ advance() # skip the @kind character
426
+
427
+ # loop is for fallthrough emulation
428
+ loop do
429
+ case c
430
+ when 't'
431
+ copy_type(@target, ! tmplargs)
432
+ break
433
+
434
+ when 'T'
435
+ copy_string("<type ")
436
+ termchar = '>'
437
+ c = 'i'; redo # fall through
438
+
439
+ when 'i'
440
+ if _begin[0,5] == '4bool'
441
+ if input() == '0'
442
+ copy_string("false")
443
+ else
444
+ copy_string("true")
445
+ end
446
+ advance()
447
+ break
448
+ else
449
+ # XXX ZZZ fall through, but not sure that its intended behaviour
450
+ # in original code
451
+ c = 'j'; redo
452
+ end
453
+
454
+ when 'j','g','e'
455
+ copy_type(@target, ! tmplargs)
456
+ @target = start.dup
457
+ assert(input() == '$'); advance()
458
+ copy_until('$', TMPLCODE)
459
+ copy_char(termchar) if termchar
460
+ break
461
+
462
+ when 'm'
463
+ copy_type(@target, ! tmplargs)
464
+ @target = start.dup
465
+ assert(input() == '$'); advance()
466
+
467
+ copy_until('$')
468
+ append '::*'
469
+ copy_until('$', TMPLCODE)
470
+ break
471
+
472
+ else
473
+ raise "Unknown template arg @kind"
474
+ end # case
475
+ end # loop
476
+
477
+ c = input()
478
+ if (c != _end)
479
+ assert(c == '$')
480
+ c = advance()
481
+ end
482
+ end # while c && c != _end
483
+ end
484
+
485
+ PEntry = Struct.new :targpos, :len
486
+
487
+ def copy_args(_end, tmplargs)
488
+ c = input()
489
+ first = true
490
+ _begin = start = nil
491
+ scanned = 0
492
+ param_table = []
493
+
494
+ tmplargs =
495
+ case tmplargs
496
+ when 0, nil, false; false
497
+ else true
498
+ end
499
+
500
+ while (c && c != _end)
501
+ if first
502
+ first = false
503
+ else
504
+ append ', '
505
+ end
506
+
507
+ _begin = @source.dup
508
+ start = @target.dup
509
+
510
+ param_table << PEntry.new
511
+ param_table.last.targpos = @target.dup
512
+
513
+ scanned = 0
514
+
515
+ while (c == 'x' || c == 'w')
516
+ # Decode 'const'/'volatile' modifiers [BCB-265738]
517
+ case c
518
+ when 'x'; copy_string("const ")
519
+ when 'w'; copy_string("volatile ")
520
+ end
521
+ scanned = 1
522
+ c = advance()
523
+ end
524
+
525
+ if (scanned && c != 't')
526
+ @source = _begin.dup
527
+ end
528
+
529
+ if (c != 't')
530
+ copy_type(@target, ! tmplargs)
531
+ else
532
+ c = advance()
533
+ ptindex = c.to_i(16) - 1
534
+ assert(param_table[ptindex].targpos)
535
+ assert(param_table[ptindex].len > 0)
536
+ copy_string param_table[ptindex].targpos[0, param_table[ptindex].len]
537
+ advance()
538
+ end
539
+
540
+ param_table.last.len = @target - param_table.last.targpos
541
+
542
+ c = input()
543
+
544
+ if (tmplargs && c == '$') # non-type template argument
545
+ termchar = 0
546
+ @target = start.dup
547
+ c = advance()
548
+ advance()
549
+ loop do # loop is for fall through emulation
550
+ case c
551
+ when 'T'
552
+ copy_string("<type ")
553
+ termchar = '>'
554
+ c = 'i'; redo # fall through
555
+
556
+ when 'i'
557
+ if _begin[0,5] == "4bool"
558
+ if (input() == '0')
559
+ copy_string("false")
560
+ else
561
+ copy_string("true")
562
+ end
563
+ advance()
564
+ break
565
+ end
566
+ c = 'j'; redo # fall through
567
+
568
+ when 'j','g','e'
569
+ copy_until('$')
570
+ copy_char(termchar) if (termchar)
571
+ break
572
+
573
+ when 'm'
574
+ copy_until('$')
575
+ append '::*'
576
+ copy_until('$')
577
+ break
578
+
579
+ else
580
+ raise "Unknown template arg @kind"
581
+ end # case
582
+ end # loop
583
+
584
+ assert(input() == '$')
585
+ c = advance()
586
+ end # if
587
+ end # while (c && c != _end)
588
+ end # def copy_args
589
+
590
+ # parse template name and arguments according to the grammar:
591
+ # tmpl_args:
592
+ # % generic_name args %
593
+ # args:
594
+ # $ new_args
595
+ # bcb3_args
596
+ def copy_tmpl_args
597
+ c = input()
598
+ save_setqual = nil
599
+ isDelphi4name = (c == 'S' || c == 'D') && (@source =~ /\A(Set|DynamicArray|SmallString|DelphiInterface)\$/)
600
+
601
+ # Output the base name of the template. We use 'copy_name' instead of
602
+ # 'copy_until', since this could be a template constructor name, f.ex.
603
+
604
+ copy_name(1)
605
+ assert(input() == ARGLIST)
606
+ advance()
607
+
608
+ # using @target[-1] will be ambiguous for ruby's string[-1] - last char of string
609
+ copy_char(' ') if (@target-1)[0] == '<'
610
+ copy_char('<')
611
+
612
+ # Copy the template arguments over. Also, save the
613
+ # '@set_qual' variable, since we don't want to mix up the
614
+ # status of the currently known qualifier name with a
615
+ # name from a template argument, for example.
616
+
617
+ save_setqual = @set_qual
618
+ @set_qual = 0
619
+
620
+ if isDelphi4name
621
+ copy_delphi4args(TMPLCODE, 1)
622
+ else
623
+ copy_args(TMPLCODE, 1)
624
+ end
625
+
626
+ @set_qual = save_setqual
627
+
628
+ # using @target[-1] will be ambiguous for ruby's string[-1] - last char of string
629
+ copy_char(' ') if (@target-1)[0] == '>'
630
+ copy_char('>')
631
+
632
+ assert(input() == TMPLCODE)
633
+ advance()
634
+ end
635
+
636
+ def isdigit c
637
+ c =~ /\A\d\Z/
638
+ end
639
+
640
+ def assert cond
641
+ unless cond
642
+ # puts
643
+ # puts
644
+ # p @source
645
+ # p @source[0..-1]
646
+ # puts
647
+ # p @target
648
+ # puts
649
+ raise
650
+ end
651
+ end
652
+
653
+ def copy_name tmplname
654
+ start = save_setqual = nil
655
+ c = input()
656
+
657
+ # Start outputting the qualifier names and the base name.
658
+
659
+ while true
660
+ if @set_qual
661
+ @base_name = @target.dup
662
+ end
663
+
664
+ # Examine the string to see what this is. Either it's a
665
+ # qualifier name, a member name, a function name, a template
666
+ # name, or a special name. We wouldn't be here if this were a
667
+ # regular name.
668
+
669
+ if isdigit(c)
670
+ # If there's a number at the beginning of a name, it
671
+ # could only be a vtable symbol flag.
672
+
673
+ flags = c[0].ord - '0'.ord + 1
674
+
675
+ @vtbl_flags << "huge" if( flags & 1 != 0 )
676
+ @vtbl_flags << "fastthis" if( flags & 2 != 0 )
677
+ @vtbl_flags << "rtti" if( flags & 4 != 0 )
678
+
679
+ @kind = (@kind & ~UM_KINDMASK) | UM_VTABLE
680
+
681
+ c = advance()
682
+ assert(c == 0 || c == '$')
683
+ end
684
+
685
+ case c
686
+ when '#' # special symbol used for cond syms
687
+ c = advance()
688
+ if c == '$'
689
+ assert(advance() == 'c')
690
+ assert(advance() == 'f')
691
+ assert(advance() == '$')
692
+ assert(advance() == '@')
693
+
694
+ copy_string("__vdflg__ ")
695
+ advance()
696
+ copy_name(0)
697
+
698
+ @kind |= UM_VIRDEF_FLAG
699
+ end
700
+ return
701
+
702
+ when QUALIFIER # virdef flag or linker proc
703
+ advance()
704
+ copy_string("__linkproc__ ")
705
+ copy_name(0)
706
+ @kind |= UM_LINKER_PROC
707
+ return
708
+
709
+ when TMPLCODE # template name
710
+ advance()
711
+ copy_tmpl_args()
712
+
713
+ if (input() != QUALIFIER)
714
+ @kind |= UM_TEMPLATE
715
+ end
716
+
717
+ when ARGLIST # special name, or arglist
718
+ return unless [0,nil,false].include?(tmplname)
719
+
720
+ c = advance()
721
+ if c == 'x'
722
+ c = advance()
723
+ if c == 'p' || c == 't'
724
+ assert(advance() == ARGLIST)
725
+ advance()
726
+ copy_string("__tpdsc__ ")
727
+ copy_type(@target, 0)
728
+ @kind = (@kind & ~UM_KINDMASK) | UM_TPDSC
729
+ return
730
+ else
731
+ raise "What happened?"
732
+ end
733
+ end # if c == 'x'
734
+
735
+ if c == 'b'
736
+ c = advance()
737
+ start = @source.dup
738
+
739
+ if (c == 'c' || c == 'd') && advance() == 't' && advance() == 'r'
740
+ assert(advance() == ARGLIST)
741
+
742
+ # The actual outputting of the name will happen
743
+ # outside of this function, to be sure that we
744
+ # don't include any special name characters.
745
+
746
+ if (c == 'c')
747
+ @kind = (@kind & ~UM_KINDMASK) | UM_CONSTRUCTOR
748
+ else
749
+ @kind = (@kind & ~UM_KINDMASK) | UM_DESTRUCTOR
750
+ end
751
+ else
752
+ @source = start.dup
753
+ copy_string("operator ")
754
+ start = @target.dup
755
+ copy_until(ARGLIST)
756
+ @target = start # no dup() here intentionally
757
+ # copy_op now will overwrite already copied encoded operator name
758
+ # i.e. "subs" => "[]"
759
+ copy_op start[0..-1]
760
+ # trim string if decoded operator name was shorter than encoded
761
+ @target[0..-1] = ''
762
+ @kind = (@kind & ~UM_KINDMASK) | UM_OPERATOR
763
+ end
764
+
765
+ elsif (c == 'o')
766
+ advance()
767
+ copy_string("operator ")
768
+ save_setqual = @set_qual
769
+ @set_qual = 0
770
+ copy_type(@target, 0)
771
+ @set_qual = save_setqual
772
+ assert(input() == ARGLIST)
773
+ @kind = (@kind & ~UM_KINDMASK) | UM_CONVERSION
774
+
775
+ elsif (c == 'v' || c == 'd')
776
+ tkind = c
777
+ c = advance()
778
+ if (tkind == 'v' && c == 's')
779
+ c = advance()
780
+ assert(c == 'f' || c == 'n')
781
+ advance()
782
+ copy_string("__vdthk__")
783
+ @kind = (@kind & ~UM_KINDMASK) | UM_VRDF_THUNK
784
+ elsif (c == 'c')
785
+ c = advance()
786
+ assert(isdigit(c))
787
+ c = advance()
788
+ assert(c == '$')
789
+ c = advance()
790
+
791
+ copy_string("__thunk__ [")
792
+ @kind = (@kind & ~UM_KINDMASK) |
793
+ (tkind == 'v' ? UM_THUNK : UM_DYN_THUNK)
794
+
795
+ copy_char(c)
796
+ copy_char(',')
797
+
798
+ while ((c = advance()) != '$'); copy_char(c); end
799
+ copy_char(',')
800
+ while ((c = advance()) != '$'); copy_char(c); end
801
+ copy_char(',')
802
+ while ((c = advance()) != '$'); copy_char(c); end
803
+ copy_char(']')
804
+
805
+ advance() # skip last '$'
806
+ return
807
+ end
808
+ else
809
+ raise "Unknown special name"
810
+ end
811
+
812
+ when '_'
813
+ start = @source.dup
814
+
815
+ if advance() == '$'
816
+ c = advance()
817
+
818
+ # At the moment there are five @kind of special names:
819
+ # frndl FL friend list
820
+ # chtbl CH catch handler table
821
+ # odtbl DC object destructor table
822
+ # thrwl TL throw list
823
+ # ectbl ETC exception context table
824
+
825
+ append '__'
826
+
827
+ case @source[0,2]
828
+ when 'FL'
829
+ copy_string("frndl")
830
+ @kind |= UM_FRIEND_LIST
831
+
832
+ when 'CH'
833
+ copy_string("chtbl")
834
+ @kind |= UM_CTCH_HNDL_TBL
835
+
836
+ when 'DC'
837
+ copy_string("odtbl")
838
+ @kind |= UM_OBJ_DEST_TBL
839
+
840
+ when 'TL'
841
+ copy_string("thrwl")
842
+ @kind |= UM_THROW_LIST
843
+
844
+ when 'EC'
845
+ copy_string("ectbl")
846
+ @kind |= UM_EXC_CTXT_TBL
847
+ end
848
+
849
+ append '__ '
850
+
851
+ while (c >= 'A' && c <= 'Z'); c = advance(); end
852
+
853
+ assert(c == '$')
854
+ assert(advance() == '@')
855
+ advance()
856
+
857
+ copy_name(0)
858
+
859
+ return
860
+ end # if advance() == '$'
861
+
862
+ @source = start.dup
863
+ copy_until(QUALIFIER, ARGLIST)
864
+
865
+ else
866
+ # default case
867
+ copy_until(QUALIFIER, ARGLIST)
868
+ end # case c
869
+
870
+ # If we're processing a template name, then '$' is allowed to
871
+ # end the name.
872
+
873
+ c = input()
874
+
875
+ assert(c == nil || c == QUALIFIER || c == ARGLIST)
876
+
877
+ if c == QUALIFIER
878
+ c = advance()
879
+
880
+ if @set_qual
881
+ @prevqual = @qualend && @qualend.dup
882
+ @qualend = @target.dup
883
+ end
884
+
885
+ append '::'
886
+
887
+ if (c == 0)
888
+ @kind = (@kind & ~UM_KINDMASK) | UM_VTABLE
889
+ end
890
+ else
891
+ break
892
+ end
893
+ end # while true
894
+ end # def copy_name
895
+
896
+ # umKind unmangle(src, dest, maxlen, qualP, baseP, doArgs)
897
+ #
898
+ # doArgs
899
+ # if this argument is non-0 (aka TRUE), it means that when
900
+ # unmangling a function name, its arguments should also be
901
+ # unmangled as part of the output name. Otherwise, only the name
902
+ # will be unmangled, and not the arguments.
903
+
904
+ def unmangle src, args = {}
905
+ return src if !src || src.empty? || src[0] != '@'
906
+
907
+ # unmangle args? defaulting to true
908
+ doArgs = args.fetch(:args, true)
909
+
910
+ @vtbl_flags = []
911
+ @kind = 0
912
+ @source_string = ''
913
+ @source = StringPtr.new(@source_string)
914
+ @result = ''
915
+ @target = StringPtr.new(@result)
916
+
917
+ #return UM_ERROR if src.size > 254
918
+ @hashstart =
919
+ case src.size
920
+ when 254 # old hash for bcc version 0x600 and earlier
921
+ 250
922
+ when 253 # new hash for bcc version 0x610 and later
923
+ 231
924
+ else
925
+ MAXBUFFLEN
926
+ end
927
+
928
+ @savechar = 0
929
+
930
+ # All mangled names start with '@' character.
931
+
932
+ src = src[1..-1] # skip initial '@'
933
+
934
+ # check for Microsoft compatible fastcall names, which are of the form:
935
+ # @funcName@<one or more digits indicating size of all parameters>
936
+ return src if src =~ /\A@.*@\d+\Z/
937
+
938
+ # Pascal names can not contain lowercase letters
939
+ if src !~ /[a-z]/
940
+ # convert uppercase Pascal names to lowercase
941
+ src.downcase!
942
+ end
943
+
944
+ # This is at LEAST a member name, if not a fully mangled template
945
+ # or function name. So, begin outputting the subnames. We set up
946
+ # the pointers in globals so that we don't have to pass
947
+ # everything around all the time.
948
+
949
+ @kind = UM_UNKNOWN
950
+ @source_string = src
951
+ @source = StringPtr.new(@source_string)
952
+ @prevqual = @qualend = @base_name = @base_end = nil
953
+ @set_qual = 1
954
+
955
+ # Start outputting the qualifier names and the base name.
956
+
957
+ @namebase = @target.dup
958
+
959
+ copy_name(0)
960
+ @set_qual = 0
961
+ @base_end = @target.dup
962
+
963
+ if (@kind & UM_KINDMASK) == UM_TPDSC || (@kind & UM_SPECMASK) != 0
964
+ p = strchr(@namebase, ' ')
965
+ assert(p)
966
+ @namebase = p + 1
967
+ end
968
+
969
+ if (@kind & UM_KINDMASK) == UM_CONSTRUCTOR || (@kind & UM_KINDMASK) == UM_DESTRUCTOR
970
+ start = nil
971
+
972
+ if (@kind & UM_KINDMASK) == UM_DESTRUCTOR
973
+ copy_char('~')
974
+ end
975
+
976
+ if !@qualend
977
+ # It's a bcc-created static constructor??
978
+ # give it a name.
979
+ copy_string("unknown")
980
+ else
981
+ if !@prevqual
982
+ start = @namebase.dup
983
+ else
984
+ start = @prevqual + 2
985
+ end
986
+ len = @qualend - start
987
+ copy_string(start, len)
988
+ end
989
+ end
990
+
991
+ # If there's a function argument list, copy it over in expanded
992
+ # form.
993
+
994
+ if input() == ARGLIST && doArgs # function args
995
+ c = advance()
996
+ assert(c == 'q' || c == 'x' || c == 'w')
997
+
998
+ # Output the function parameters, and return type in the case
999
+ # of template function specializations.
1000
+
1001
+ @set_qual = 0
1002
+ @adjust_quals = 1
1003
+
1004
+ copy_type(@namebase, 0)
1005
+
1006
+ if ((@kind & UM_KINDMASK) == UM_UNKNOWN)
1007
+ @kind |= UM_FUNCTION
1008
+ end
1009
+
1010
+ elsif ((@kind & UM_KINDMASK) == UM_UNKNOWN)
1011
+ @kind |= UM_DATA
1012
+ elsif @vtbl_flags.any?
1013
+ copy_string(" (" + @vtbl_flags.join(", ") + ")")
1014
+ end
1015
+
1016
+ # Put some finishing touches on the @kind of this entity.
1017
+
1018
+ if (@qualend)
1019
+ @kind |= UM_QUALIFIED
1020
+ end
1021
+
1022
+ # If the user wanted the qualifier and base name saved, then do it now.
1023
+
1024
+ # TODO
1025
+ # if (@kind & UM_ERRMASK) == 0
1026
+ # if @qualend
1027
+ # len = @qualend - @namebase
1028
+ # @qualP = @namebase[0, len]
1029
+ # end
1030
+ #
1031
+ # if @base_name
1032
+ # len = @base_end - @base_name
1033
+ # @baseP = @base_name[0, len]
1034
+ # end
1035
+ # end
1036
+
1037
+ # move '__fastcall' to start of the string if its found in middle of string
1038
+ pos = @result.index(" __fastcall ")
1039
+ if pos && pos != 0
1040
+ @result = "__fastcall " + @result.sub("__fastcall ", "")
1041
+ end
1042
+
1043
+ # sometimes const args are marked "const const",
1044
+ # original tdump.exe tool also have this bug
1045
+ @result.gsub! "const const ", "const "
1046
+
1047
+ # doArgs implicitly includes calling convention, but '__tpdsc__' is always
1048
+ # returned by original code, so strip it here if doArgs == false
1049
+ unless doArgs
1050
+ @result.sub! /\A__tpdsc__ /,''
1051
+ end
1052
+
1053
+ @result
1054
+ end
1055
+
1056
+ # same as 'unmangle', but catches all exceptions and returns original name
1057
+ # if can not unmangle
1058
+ def safe_unmangle name, *args
1059
+ unmangle name, *args
1060
+ rescue
1061
+ name
1062
+ end
1063
+
1064
+ def self.unmangle *args
1065
+ new.unmangle(*args)
1066
+ end
1067
+
1068
+ def self.safe_unmangle *args
1069
+ new.safe_unmangle(*args)
1070
+ end
1071
+ end # class Unmangler
1072
+
1073
+ ###################################################################
1074
+
1075
+ if $0 == __FILE__
1076
+ require 'awesome_print'
1077
+ require 'pp'
1078
+
1079
+ def check src, want, do_args = true
1080
+ u = Unmangler::Borland.new
1081
+ got = nil
1082
+ begin
1083
+ got = u.unmangle(src, do_args)
1084
+ rescue
1085
+ pp u
1086
+ raise
1087
+ end
1088
+ if got == want
1089
+ print ".".green
1090
+ else
1091
+ puts "[!] want: #{want.inspect}"
1092
+ puts "[!] got: #{got.inspect}"
1093
+ pp u
1094
+ puts
1095
+ exit 1
1096
+ end
1097
+ end
1098
+
1099
+ check "@afunc$qxzcupi", "afunc(const signed char, int *)"
1100
+ check "@foo$qpqfi$d", "foo(double (*)(float, int))"
1101
+ check "@myclass@func$qil","myclass::func(int, long)"
1102
+
1103
+ check "@Sysinit@@InitExe$qqrpv",
1104
+ "__fastcall Sysinit::__linkproc__ InitExe(void *)"
1105
+
1106
+ check "@Forms@TApplication@SetTitle$qqrx17System@AnsiString",
1107
+ "__fastcall Forms::TApplication::SetTitle(const System::AnsiString)"
1108
+
1109
+ check "@Forms@TApplication@CreateForm$qqrp17System@TMetaClasspv",
1110
+ "__fastcall Forms::TApplication::CreateForm(System::TMetaClass *, void *)"
1111
+
1112
+ check "@System@@LStrCatN$qqrv", "__fastcall System::__linkproc__ LStrCatN()"
1113
+
1114
+ check "@System@DynArraySetLength$qqrrpvpvipi",
1115
+ "__fastcall System::DynArraySetLength(void *&, void *, int, int *)"
1116
+
1117
+ check "@System@Variant@PutElement$qqrrx14System@Variantxixi",
1118
+ "__fastcall System::Variant::PutElement(System::Variant&, const int, const int)"
1119
+
1120
+ check "@Windows@HwndMSWheel$qqrruit1t1rit4",
1121
+ "__fastcall Windows::HwndMSWheel(unsigned int&, unsigned int&, unsigned int&, int&, int&)"
1122
+
1123
+ # IDA uses '__int64' instead of 'long long'
1124
+ check "@Sysutils@TryStrToInt64$qqrx17System@AnsiStringrj",
1125
+ "__fastcall Sysutils::TryStrToInt64(const System::AnsiString, long long&)"
1126
+
1127
+ check "@Sysutils@Supports$qqrpx14System@TObjectrx5_GUIDpv",
1128
+ "__fastcall Sysutils::Supports(System::TObject *, _GUID&, void *)"
1129
+
1130
+ check "@std@%vector$51boost@archive@detail@basic_iarchive_impl@cobject_id69std@%allocator$51boost@archive@detail@basic_iarchive_impl@cobject_id%%@$bsubs$qui",
1131
+ "std::vector<boost::archive::detail::basic_iarchive_impl::cobject_id, std::allocator<boost::archive::detail::basic_iarchive_impl::cobject_id> >::operator [](unsigned int)"
1132
+
1133
+ check "@Dbcommon@GetTableNameFromSQLEx$qqrx17System@WideString25Dbcommon@IDENTIFIEROption",
1134
+ "__fastcall Dbcommon::GetTableNameFromSQLEx(const System::WideString, Dbcommon::IDENTIFIEROption)"
1135
+
1136
+ # check "@Sysutils@Supports$qqrx45System@_DelphiInterface$t17System@IInterface_rx5_GUIDpv",
1137
+ # "__fastcall Sysutils::Supports(const System::DelphiInterface<System::IInterface>, _GUID const &, void *)"
1138
+
1139
+ ####################################
1140
+ # w/o args
1141
+ ####################################
1142
+
1143
+ check "@Dbcommon@GetTableNameFromSQLEx$qqrx17System@WideString25Dbcommon@IDENTIFIEROption",
1144
+ "Dbcommon::GetTableNameFromSQLEx",
1145
+ false
1146
+
1147
+ check "@std@%vector$51boost@archive@detail@basic_iarchive_impl@cobject_id69std@%allocator$51boost@archive@detail@basic_iarchive_impl@cobject_id%%@$bsubs$qui",
1148
+ "std::vector<boost::archive::detail::basic_iarchive_impl::cobject_id, std::allocator<boost::archive::detail::basic_iarchive_impl::cobject_id> >::operator []",
1149
+ false
1150
+
1151
+ puts
1152
+ end