unmangler 0.0.1

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