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
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
time | code | comment |
---|---|---|
0.147 | s.bytes.to_a | most 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. Как бороться?
Для того чтобы бороться надо сначала узнать с кем конкретно бороться :) А мы пока узнали только тот факт что память течет. Далее нужно определить виновника. Для этого есть как минимум три ортогональных способа:
- valgrind – для поиска утечек в самом ruby и экстеншнах. для нормальной работы требует специально-фигурно-выпиленный бинарник ruby. патчи тут. К текущей версии ruby (ruby-1.8.7-p249) подходят с трудом.
- bleak_house – инструмент для отслеживания аллокейшнов ruby-объектов. Подходит для Rails.
- метод эмпирической дихотомии – универсальный способ :) – во-первых, надо определить что память течет (п.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-ы.
причем здесь 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, но там всё разжевывается под винду и вижуалстудию
скачать клип с vimeo
Если всё получилось – плюсаните карму тут или кликните справа на картинку с рублем.
а теперь тоже самое на 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\"") |