zed.0xff.me

demangle MSVC, Delphi & C++Builder mangled function names with pure Ruby

Ever wanted to convert @afunc$qxzcupi or ??3@YAXPAX@Z to something more human-readable?

(ofcourse you wanted, and you know about tdump.exe and undname.exe :)

And now you can do it using pure ruby, thanks to unmangler gem:

Unmangling Borland mangled names

1
2
3
4
5
6
7
8
require 'unmangler'

puts Unmangler.unmangle "@afunc$qxzcupi"
puts Unmangler.unmangle "@Forms@TApplication@SetTitle$qqrx17System@AnsiString"

# output:
# afunc(const signed char, int *)
# __fastcall Forms::TApplication::SetTitle(const System::AnsiString)

Unmangling MSVC mangled names

1
2
3
4
5
6
7
8
require 'unmangler'

puts Unmangler.unmangle "??3@YAXPAX@Z"
puts Unmangler.unmangle "?AFXSetTopLevelFrame@@YAXPAVCFrameWnd@@@Z"

# output:
# void __cdecl operator delete(void *)
# void __cdecl AFXSetTopLevelFrame(class CFrameWnd *)

And now w/o arguments

1
2
3
4
5
6
7
8
9
require 'unmangler'

puts Unmangler.unmangle "@Forms@TApplication@SetTitle$qqrx17System@AnsiString", :args => false

# outputs "Forms::TApplication::SetTitle"

puts Unmangler.unmangle "?AFXSetTopLevelFrame@@YAXPAVCFrameWnd@@@Z", :args => false

# outputs "AFXSetTopLevelFrame"

Links

  1. unmangler gem on rubygems.org
  2. unmangler sources on github.com

TBD: GCC support

Advanced Ruby: percent-literals

most used %-literals:

% code result description
%q %q(all `quotes` 'are' "ok") "all `quotes` 'are' \"ok\"" creates String
%r %r(i_am/a/regexp) /i_am\/a\/regexp/ creates Regexp
%w %w(abc def ghi) ["abc", "def", "ghi"] splits string into Array
%x %x(ls -a /tmp) ".\n..\nfile1\nfile2\n" alias of `cmd`

highlighted

1
2
3
4
5
6
7
%q(all `quotes` 'are' "ok") => "all `quotes` 'are' \"ok\""

%r(i_am/a/regexp)           => /i_am\/a\/regexp/

%w(abc def ghi)             => ["abc", "def", "ghi"]

%x(ls -a /tmp)              => ".\n..\nfile1\nfile2\n"

interpolation

1
2
3
4
5
6
7
8
9
10
11
# lowercase 'w': as-is
%w'a #{2+2} b' => ["a", "\#{2+2}", "b"] 

# uppercase 'W': interpolate
%W'a #{2+2} b' => ["a", "4", "b"] 

# lowercase 'q': as-is
%q'a #{2+2} b' => "a \#{2+2} b" 

# uppercase 'Q': interpolate
%Q'a #{2+2} b' => "a 4 b" 

other %-literals

1
2
3
4
5
6
7
8
# %s: convert to symbol
%s'foo' => :foo 

# %i: convert to array of symbols - not released yet, will be in Ruby 2.0 ?
%i'foo bar baz' => [:foo, :bar, :baz]

# '%' w/o any letter - alias for %Q
%'a #{2+2} b' => "a 4 b"

Advanced Ruby: break(value) & next(value)

1. break(value)

break accepts a value that supplies the result of the expression it is “breaking” out of:

1
2
3
4
5
  result = [1, 2, 3].each do |value|
    break value * 2 if value.even?
  end

  p result # prints 4

2. next(value)

next accepts an argument that can be used the result of the current block iteration:

1
2
3
4
5
6
7
  result = [1, 2, 3].map do |value|
    next value if value.even?

    value * 2
  end

  p result # prints [2, 2, 6]

Ruby: fastest way of converting string into array of characters

timecodecomment
0.147 s.bytes.to_amost fastest, but returns ASCII codes instead of chars
0.242 s.chars.to_a FASTEST
0.257 Array(s.chars)
0.265 a=[]; s.size.times{ |i| a<<s[i] }
0.268 a=[]; s.chars.each{ |c| a<<c }
0.278 s.bytes.map(&:chr)
0.513 s.scan(/./)
0.775 s.split(//)
0.795 s.split('') SLOWEST

first column is time of 100.000 iterations on Core i5 1.7GHz

code: bench-split.rb

раскодируем Trojan.Siggen3.35000

лечение

обнаружение

Попался мне как-то в руки троянчик.
Вот его анализ на pedump.me
Вот так он обнаруживается различными антивирусами:

AntiVir: TR/Offend.6610086.8
BitDefender: Trojan.Generic.6610086
ClamAV: Trojan.Dropper-31300
DrWeb: Trojan.Siggen3.35000
Emsisoft: Trojan.SuspectCRC!IK
F-Secure: Trojan.Generic.6610086
Fortinet: W32/Filecoder.AA
GData: Trojan.Generic.6610086
Ikarus: Trojan.SuspectCRC
K7AntiVirus: Riskware
Kaspersky: UDS:DangerousObject.Multi.Generic
McAfee: Artemis!2A8242105FED
NOD32: Win32/Filecoder.AA
Norman: W32/Malware.WVNX
Panda: Trj/CI.A
TheHacker: Trojan/Filecoder.ab

полный отчет на virustotal.com

анализ

троян портит JPG, DOC, XLS файлы. Обнаружить испорченные файлы можно визуально по следующему куску данных в конце файла:

1
2
3
4
5
6
7
8
9
10
001a2cfb:  34 36 34 36 34 34 33 38  34 36 34 36 34 35 33 31  |4646443846464531|
001a2d0b:  33 32 33 35 34 36 34 36  34 36 34 36 33 30 33 31  |3235464646463031|
001a2d1b:  33 30 33 30 33 30 33 30  33 30 33 30 33 30 33 30  |3030303030303030|
001a2d2b:  33 30 33 30 33 30 33 30  33 30 33 30 33 30 33 30  |3030303030303030|
001a2d3b:  33 30 33 30 33 30 33 30  33 30 33 30 33 30 33 30  |3030303030303030|
001a2d4b:  33 30 33 30 33 30 33 30  33 30 33 30 33 30 33 30  |3030303030303030|
001a2d5b:  33 30 33 30 14 00 00 00  00 fb 2c 1a 00 14 00 04  |3030......,.....|
001a2d6b:  00 00 0f 2d 1a 00 14 00  08 00 00 23 2d 1a 00 14  |...-.......#-...|
001a2d7b:  00 0c 00 00 37 2d 1a 00  14 00 10 00 00 4b 2d 1a  |....7-.......K-.|
001a2d8b:  00 05 

(конкретные данные различны в зависимости от содержимого файла)

результат бинарного сравнения нормального (слева) и испорченного (справа) файла:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
00000000: ff 54
00000001: d8 2f
00000002: ff 54
00000003: e1 38
00000004: 25 7a
00000400: ff 54
00000401: ff 56
00000402: 01 56
00000403: 00 57
00000404: 00 55
00000800: 00 54
00000801: 00 51
00000802: 00 54
00000803: 00 51
00000804: 00 54
00000c00: 00 54
00000c01: 00 51
00000c02: 00 54
00000c03: 00 51
00000c04: 00 54
00001000: 00 54
00001001: 00 51
00001002: 00 54
00001003: 00 51
00001004: 00 54

[!]           168_ok.jpg is  1715451 bytes long
[!]          168_bad.jpg is  1715597 bytes long

фактически троян портит по 5 байт в файле с интервалом в 1024 байта. но не просто портит, а записывает оригинал в конец испорченного файла. вот наглядный вид чего он там понаписал:

1
2
3
4
5
6
001a2d5f:  14  00 00 00 00  fb 2c 1a 00  |......,..|
001a2d68:  14  00 04 00 00  0f 2d 1a 00  |......-..|
001a2d71:  14  00 08 00 00  23 2d 1a 00  |.....#-..|
001a2d7a:  14  00 0c 00 00  37 2d 1a 00  |.....7-..|
001a2d83:  14  00 10 00 00  4b 2d 1a 00  |.....K-..|
001a2d8c:  05         

good old "fc /b" (byte-by-byte binary files compare) in ruby

Code (save as fc.rb)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/ruby
# simple binary file compare (c) http://zed.0xff.me
# like good old DOS "fc /b"

if ARGV.size < 2
  puts("[!] gimme at least two filenames")
  exit
end

handles = ARGV.map{ |fname| open(fname) }

while !handles.any?(&:eof)
  bytes = handles.map(&:readbyte)
  if bytes.uniq.size > 1
    @diff = true
    printf "%08x:"+" %02x"*handles.size+"\n", handles[0].pos-1, *bytes
  end
end

unless handles.all?(&:eof)
  @diff = true
  puts
  ARGV.each do |fname|
    printf "[!] %20s is %8d bytes long\n", fname, File.size(fname)
  end
end

puts "[.] all files are identical" unless @diff

Sample output

1
2
3
4
5
6
7
8
9
10
11
12
#fc.rb black.png white.png 
0000003e: 00 ff
0000003f: 00 ff
00000040: 00 ff
00000064: 23 92
00000065: 79 0a
00000066: 01 04
00000067: 05 02
00000068: 7f 83
00000069: 83 a9
0000006a: dc c5
0000006b: 13 01

поиск утечек памяти в ruby/rails

0. Введение

Для начала – что такое классические утечки памяти? Это когда мы (или не мы) сделали malloc(), но забыли сделать free(). Но на деле эта память уже не используется и должна бы быть давно уже возвращена системе через free().

Что такое утечки памяти в ruby? Это висящие в памяти объекты(А), на которые есть ссылки с каких-то глобальных объектов(Б), но фактически (А) уже никому не нужны, а с них, в свою очередь еще есть кучи и кучи ссылок на другие объекты, которые занимают туеву хучу памяти, и не могут быть освобождены сборщиком мусора, потому что на них есть ссылки с других объектов.

Сложно? Сейчас будет еще сложнее :) Плюс к упомянутым выше специфичным для ruby утечкам памяти из-за ссылок на объекты, в ruby также могут быть (и есть!) и классические утечки памяти. В основном в различных экстеншнах, обычно подключаемых через gem’ы. Но и в самом интерпретаторе ruby вероятно где-то еще остались необнаруженные утечки.. только и количество и вероятность их обнаружения на пару-тройку порядков ниже, чем в сторонних gem’ах. Потому что в разработке ruby явно участвует больше и людей и тестеров, чем в разработке любого gem’а.

1. Как узнать что память течет?

Банально :) Смотрим периодически на наш ruby-процесс через ps или top, и если размер памяти процесса постоянно увеличивается, и вскоре начинает занимать всю физическую память + своп – то серьезные утечки памяти скорее всего есть.
Но это способ эмпирический, а вот более практический – нужно ограничить процессу память каким-нибудь значительным объемом (например 512Мб)

1
  Process.setrlimit Process::RLIMIT_AS, 512*1024*1024, 512*1024*1024

и погонять процесс. Если он через какое-то время (минуты, часы, дни, месяцы, … :) выпадает с криком [FATAL] failed to allocate memory – значит утечки есть.
Этот способ не сработает, или сработает неправильно, если работа процесса связана с обработкой больших объемов данных (сопоставимых с 512Мб в данном случае).

2. Как бороться?

Для того чтобы бороться надо сначала узнать с кем конкретно бороться :) А мы пока узнали только тот факт что память течет. Далее нужно определить виновника. Для этого есть как минимум три ортогональных способа:

  1. valgrind – для поиска утечек в самом ruby и экстеншнах. для нормальной работы требует специально-фигурно-выпиленный бинарник ruby. патчи тут. К текущей версии ruby (ruby-1.8.7-p249) подходят с трудом.
  2. bleak_house – инструмент для отслеживания аллокейшнов ruby-объектов. Подходит для Rails.
  3. метод эмпирической дихотомии – универсальный способ :) – во-первых, надо определить что память течет (п.1), во-вторых, берем большой топор и начинаем вырезать или комментировать куски кода.. после каждой ампутации заново проверям осталась ли утечка. если осталась – продолжаем ампутацию, если исчезла – смотрим внутрь отрезанного куска, вплоть до локализации “текущего” метода экстеншна либо присвоения глобальной переменной ($global_var) или переменной класса (@@class_var).

P.S. Все утечки актуальны только во время исполнения приложения. Как только приложение завершается, вся память всегда возвращается системе.

fork() в нужный момент экономит время

rspec_bisect и с чем его едят

Есть у меня скриптик rspec_bisect, предназначенный для выискивания в rails-проекте спеков, которые в процессе своего выполнения что-то глобально меняют/трогают/expect-ают или stub-ают (а то и, даже страшно сказать, any_instance-ют!..) в результате чего потом другие спеки успешно натыкаются на расставленные грабли, и FAIL-ятся с довольно фантастическими ошибками.

Показанием к применению данного скрипта можно считать случай когда запуск одной спеки через spec /spec/models/first_model_spec.rb завершается успешно и без FAIL-ов, а на запуск ее же вместе с другими спеками через rake spec получаем гарантированные FAIL-ы.

Пример работы rspec_bisect.

причем здесь fork() ?..

Притом что изначально для поиска иголки в стоге сена я запускал наборы спеков через `spec /spec/models/m1_spec.rb spec/models/m2_spec.rb ...`, т.е. через вызов system() (чтобы получить изолированное окружение, не влияющее ни на родителя, ни на соседей).

При этом каждый раз рельсы упорно грузили весь свой арсенал, каждый раз отъедая что-то около 10с (на довольно тяжелом проекте).

Зерном идеи оптимизации послужила давняя попытка запуска Spork. Почему-то она не никаким приростом скорости не увенчалась, но полезный осадок в виде особенностей работы Kernel.fork оставила.

Суть в том (если кто не знает), что “fork() creates a new process by duplicating the calling process.” © man 2 fork. Т.е. если вовремя форкнуться, то не надо 10 раз грузить весь Rails environment, достаточно будет загрузить его всего один раз, а все запуски тестов делать уже в fork-нутых child-ах.

На том и порешили. После непродолжительной борьбы rspec сдался и раскололся чем и как он запускает спеки ( оказалось – через at_exit.. старый извращенец :), и был слегка поmonkeyпатчен, чтоб не делал лишних телодвижений.

Сравнение запусков “до” и “после”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
### ДО ###
[.] running   62 specs.. Done. ( 19s) (105/0/4) : target OK
[.] running   63 specs.. Done. ( 60s) (384/2/6) : target FAIL
[.] running   32 specs.. Done. ( 35s) (157/0/2) : target OK
[.] running   32 specs.. Done. ( 47s) (236/2/4) : target FAIL
[.] running   16 specs.. Done. ( 35s) (129/2/1) : target FAIL
[.] running    8 specs.. Done. ( 28s) (73/0)    : target OK
[.] running    9 specs.. Done. ( 22s) (65/2/1)  : target FAIL
[.] running    5 specs.. Done. ( 18s) (52/2/1)  : target FAIL
[.] running    3 specs.. Done. ( 17s) (33/0)    : target OK
[.] running    3 specs.. Done. ( 17s) (28/2/1)  : target FAIL
[.] running    2 specs.. Done. ( 17s) (27/2/1)  : target FAIL

### ПОСЛЕ ###
[.] running   62 specs.. Done. (  9s) (105/0/4) : target OK
[.] running   63 specs.. Done. ( 49s) (384/2/6) : target FAIL
[.] running   32 specs.. Done. ( 21s) (157/1/2) : target OK
[.] running   32 specs.. Done. ( 34s) (236/3/4) : target FAIL
[.] running   16 specs.. Done. ( 24s) (129/3/1) : target FAIL
[.] running    8 specs.. Done. ( 20s) (73/1)    : target OK
[.] running    9 specs.. Done. (  9s) (65/3/1)  : target FAIL
[.] running    5 specs.. Done. (  8s) (52/3/1)  : target FAIL
[.] running    3 specs.. Done. (  4s) (33/1)    : target OK
[.] running    3 specs.. Done. (  8s) (28/3/1)  : target FAIL
[.] running    2 specs.. Done. (  7s) (27/3/1)  : target FAIL

В результате время запуска тестового набора сократилось с 315с до 193с, т.е. чуть больше чем на треть.

rspec bisect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#./rspec-bisect.rb spec/**/*_spec.rb spec/controllers/spaces/tickets_controller_spec.rb
[.] rspec runner: ./script/spec
[.] target spec : spec/controllers/spaces/tickets_controller_spec.rb
[.] 123 candidate specs
[.] running   62 specs.. Done. ( 19s) (105/0/4)        : target OK
[.] running   63 specs.. Done. ( 64s) (386/2/6)        : target FAIL
[.] running   32 specs.. Done. ( 34s) (157/0/2)        : target OK
[.] running   32 specs.. Done. ( 43s) (238/2/4)        : target FAIL
[.] running   16 specs.. Done. ( 38s) (129/2/1)        : target FAIL
[.] running    8 specs.. Done. ( 27s) (73/0)           : target OK
[.] running    9 specs.. Done. ( 19s) (65/2/1)         : target FAIL
[.] running    5 specs.. Done. ( 19s) (52/2/1)         : target FAIL
[.] running    3 specs.. Done. ( 16s) (33/0)           : target OK
[.] running    3 specs.. Done. ( 18s) (28/2/1)         : target FAIL
[.] running    2 specs.. Done. ( 17s) (27/2/1)         : target FAIL
[*] found matching spec: spec/models/mailman_spec.rb

Download @ github.

You MUST free memory you got from ALLOC_N & friends

It’s not documented anywhere, but you must call “xfree()” (note ‘x’ there) on memory blocks you got from ALLOC_N & friends.
Ruby will not free that memory automatically during it’s usual GC process.

Guys from ruby-talk and rubyforge also noticed that.

sample code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
VALUE method_parse(VALUE self, VALUE text) {
    VALUE s;
    char *p    = RSTRING(text)->ptr;
    in_buf_len = RSTRING(text)->len;

    // [skipped some code here]

    buf = ALLOC_N(char, bufsize); 

    // protect buf from GC (theoretically)
    rb_iv_set(self,"@obj",Data_Wrap_Struct(rb_cData,NULL,NULL,buf));

    // [skipped some hard work here]

    // make ruby string from our char[] data
    s = rb_str_new(buf,bufptr-buf);

    // cleanup
    rb_iv_set(self,"@obj",Qnil);
    xfree(buf);
    buf  = NULL;
    bufsize = 0;

    return s;
}

benchmark: Ruby's rb_str_cat() vs C's strcat()

В связи с началом написания расширений для ruby решил провести бенчмарк рубевской функции rb_str_cat() VS сишной родной strcat()
Результат меня немало удивил:

1
2
strcat     3.360s
rb_str_cat 0.002s

Тесты проводились в тяжелых полевых условиях – испытуемым надо было 100000 (сто тысяч) раз приклеить к строке(изначально пустой) один заранее заданный символ.
И ruby сделал это, грубо говоря, в 1500 раз быстрее!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define N 100000

VALUE test_concat_rb_str_cat(VALUE self){
    int i;
    VALUE s = rb_str_new2("");

    for(i=0;i<N;i++) rb_str_cat(s,"t",1);
    return s;
}

VALUE test_concat_strcat(VALUE self){
    int i;
    char buf[N+2];

    *buf=0;
    for(i=0;i<N;i++) strcat(buf,"t");
    return rb_str_new2(buf);
}

На самом деле никакого подвоха тут нет – просто в руби объект “строка” содержит в себе не только указатель на байты содержимого, но и длину строки тоже, а сишный вариант при этом каждый раз считает длину строки. Т.е. ему, бедному, пришлось перемолотить при этом суммарно почти 5 гигабайт, каждый раз считая длину строки начиная с самого первого символа, и до 100-тысячного в конце.

Прелесть руби здесь в том что не надо думать о памяти – он сам выделит сколько надо и освободит когда будет можно. В сях это слегка сложнее, но можно доработать сишный код так, чтобы он и память выделял динамически, и длину строки стопицот раз не пересчитывал. Например, так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
VALUE test_concat_char(VALUE self){
    int i;
    int bufsize = 0x1000;
    char *buf = malloc(bufsize);
    char *p=buf;
    VALUE s;

    for(i=0;i<N;i++){
        *p++ = 't';
        if( p-buf >= bufsize ){
            char *oldbuf = buf;
            bufsize += 0x1000;
            buf = realloc(buf, bufsize);
            p = buf + (p-oldbuf);
        }
    }
    s=rb_str_new(buf,p-buf);
    free(buf);
    return s;
}

Скорость обработки получается уже совсем другая:

1
2
3
strcat     3.36732s
rb_str_cat 0.00215s
char       0.00013s

Вот теперь сишный код в 16 раз быстрее чем руби, и в 24000 с лишним раз быстрее, чем в начале.

PS: это всё на Linux 2.6.31-gentoo-r6-zz x86_64 Intel(R) Core(TM)2 Duo CPU E8400 @ 3.00GHz

пишем на C расширение для ruby

Это не просто, а очень просто (как два пальца об асфальт два файла написать):

1й файл: mytest.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "ruby.h"
void Init_mytest();
VALUE method_sayhello(VALUE);

VALUE mytest = Qnil;

void Init_mytest() {
  mytest = rb_define_module("MyTest");
  rb_define_method(mytest, "sayhello", method_sayhello, 0);
}

VALUE method_sayhello(VALUE self) {
  puts("Hello World!");
  return Qnil;
}

2й файл: extconf.rb

1
2
3
4
require 'mkmf'
extension_name = 'mytest'
dir_config(extension_name)
create_makefile(extension_name)

запуск:

1
2
3
4
5
6
7
8
9
10
11
12
#ruby extconf.rb 
creating Makefile
#make
x86_64-pc-linux-gnu-gcc -shared -o mytest.so mytest.o -L. -L/usr/lib64 -Wl,-R/usr/lib64 -L. -Wl,-O1 -rdynamic -Wl,-export-dynamic    -Wl,-R -Wl,/usr/lib64 -L/usr/lib64 -lruby18  -lpthread -lrt -ldl -lcrypt -lm   -lc
#irb
irb(main):001:0> require 'mytest'
=> true
irb(main):002:0> include MyTest
=> Object
irb(main):003:0> sayhello
Hello World!
=> nil

// пример содран с Введения в расширения Ruby на C, но там всё разжевывается под винду и вижуалстудию

Phuby on Phails: PHP и Ruby в одном флаконе! жесть!

код и видео
видео в на порядок выразительнее кода :)

// thanks to Rail0rz

скачать клип с vimeo

Введите ID ролика или ссылку вида “http://www.vimeo.com/800978”:

Если всё получилось – плюсаните карму тут или кликните справа на картинку с рублем.

а теперь тоже самое на Ruby :)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env ruby

%w[rexml/document open-uri].each { |lib| require lib }
include REXML

if ARGV.size < 1
   STDERR.puts "usage: #{File.basename($0,'.rb')} code/uri"
   exit 1
end

videoCode = ARGV[0].split('/')[-1]
xmlURL = "http://www.vimeo.com/moogaloop/load/clip:#{videoCode}"

xml = (Document.new(open(xmlURL))).root
videoCaption = xml.elements['video'].elements['caption'].text
videoCaption.gsub!(/\//,'_') # In case there is a /

req = []
%w[request_signature request_signature_expires].each do |e|
   req << xml.elements[e].text
end

downloadLink = "http://www.vimeo.com/moogaloop/play/clip:#{videoCode}/#{req[0]}/#{req[1]}/?q=sd"

Kernel.exec("wget -c #{downloadLink} -O \"#{videoCaption}.flv\"")