Binary Hacks memo

『Binary Hacks』(高林哲, 鵜飼文敏, 佐藤祐介, 浜地慎一郎, 首藤一幸)での覚え書き(とかつっこみとか), この本は手を動かしてみないと面白さ半減やね.

#2 p. 10 リフレクション
ここだけじゃなくて本全体で"C"と"C言語"が混ざってる. 多人数なので仕方ないのかもしれないけどこれぐらいの基本用語は統一してほしかったなあ.
#4 p. 17 odの利用例
Cの配列の最後のコンマはあっても文法上許されてるから(enumの最後のコンマは駄目)わざわざ削除する必要はないと思う. 無論削除するのはありだけど.
C99では大丈夫とのご指摘. そうでした. でも今のところC++では駄目なんですよね.
#16 p. 61 strip
私はgccに-sつけてstripさせちゃうことが多いな.
>% gcc hello.c && wc -c ./a.out
>  12893 ./a.out
>% gcc -s hello.c && wc -c ./a.out
>   2748 ./a.out
#18 p. 66 try
int func()
{ // この括弧と
    try {
    } catch (...) {
    }
} // この括弧が省略できる
のtry節を囲む{}は省略できるんや. 知らんかった. へぇ.
#34 p. 123 mprotect()
ヒープやスタック上のコードを実行するために使う. 手持ちのkernel 2.4.32とWindows Xpでは mprotect() 無しでも実行できたがkernel 2.6では実行時エラー. なるほど必要だ. ところで allow_execution() の中の sysconf() のプロトタイプ宣言での返り値は long なのにわざわざ int にキャストしてるのはなぜ? キャスト不要(むしろ有害)だよね.
    long pagesize = (int)sysconf(_SC_PAGESIZE);
以前ちょっと遊んだJITによる割り算再び. そのとき使ったsoftwireは今無くなっちゃってるので, フルスクラッチで作り直したXbyakを使う. udiv.generate(d) のところで内部に持つ char 配列に割り算コードを生成して mprotect() で実行属性を与えて getCode() でポインタを返す. ジェネレータ部分を昔3D Now!用アセンブラ作ったときのをまじめに改良して使えるものにしてみようかな.
class Udiv : public Xbyak::CodeGenerator {
    // 最適化された割り算コードの動的生成
    void generate(unsigned int dividend)
    {
        ...
        mov(edx, int(m));
        mul(edx);
        if (a) {
            add(eax, int(m));
            adc(edx, 0);
        }
        if (len) {
            shr(edx, len);
        }
        mov(eax, edx);
        ret();
    }

    ...

    Udiv udiv;
    udiv.generate(d);
    int (*div)(unsigned int) = (int (*)(unsigned int))udiv.getCode();

    for (int i = 0; i < 10000; i++) {
        int a = i / d;
        int b = div(i);
        assert(a == b);
    }
#42 p. 154 strict-aliasing rule
変数を, その変数と異なるサイズで読むなんてよくやってた気がする. なんと未定義だったのか. まあ, P4になってからペナルティがつくようになったので最近は極力しないけど.
main()
{
    int a = 3;
    *(short*)&a = 4;
}
>gcc -O2 -Wstrict-aliasing=2 t.c
>warning: dereferencing type-punned pointer will break \
>strict-aliasing rules
#77 p. 300 -finstrument-functions
簡易プロファイラを作るとき便利だな. C++で使うときは下記のようにプロトタイプ宣言をしないと駄目だった. 関数名も固定なのね.
extern "C" {
void __cyg_profile_func_enter(void *funcAddr, void *callSite)
__attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *funcAddr, void *callSite)
__attribute__((no_instrument_function));
}

void __cyg_profile_func_enter(void *funcAddr, void *callSite)
{
    printf("enter=%p, from %p\n", funcAddr, callSite);
}

void __cyg_profile_func_exit(void *funcAddr, void *callSite)
{
    printf("leave=%p, from %p\n", funcAddr, callSite);
}
ちなみにこの関数の中で別の関数func()を呼ぶとその関数の中でまた __cyg_profile_func_enter() が呼ばれて無限ループになった. だから func() の宣言にも __attribute__((no_instrument_function)) をつけないといけない. でもSTLにつけてられないよなあ. 困った. というかこの attribute は本質ではなくコンパイルオプション -finstrument-functions が大事なのか. というわけで HACK #64, #67, #68 あたりと組み合わせ作ってみた(bfd.tgz). なるほど, 面白いね.
>g++ -O2 -c bfdsymbol.cpp
>g++ -finstrument-functions dump_symbols.cpp -o bfd -lbfd bfdsymbol.o

>cat dump_symbols.cpp
#include <stdio.h>
#include <stdlib.h>
#include "bfdsymbol.h"

extern "C" void func1()
{
}

void func2()
{
    func1();
    printf("this function is %s\n", GetSymbolString((void*)func2));
}

void func3()
{
    func2();
}

void func4()
{
    func2();
    func3();
}

int main(int argc, char *argv[])
{
    func3();
    func4();
    func2();
    return 0;
}

>./bfd
in main [0x8048e38]
  in func3 [0x8048d70]
    in func2 [0x8048cf2]
      in func1 [0x8048cc4]
this function is func2
  in func4 [0x8048dd2]
    in func2 [0x8048cf2]
      in func1 [0x8048cc4]
(snip)
#99 p. 372 FPUの例外マスク
WindowsでFPUの例外マスクを変更するなら精度の指定でも使った _controlfp() が使える. それはともかく feenableexcept() や fnstcw を使うだけではSSE2の例外処理を変更できない*. 最近のコンパイラはFPUの代わりに積極的にSSE2を使うようになっているから(無意識に使う場合もあるだろうから)場合によっては一緒に設定した方がいいと思う.(午後だと全部無効にしてたけど).
*feenableexcept()の方はひょっとしてSSE2にも対応してるかと思ったけどそうじゃなかった@gcc 4.1.1.
.
追記. VC2005だと__control87_2という命令があってアセンブラを使わなくても設定できる.
; for FPU
%define IM 0x01
%define DM 0x02
%define ZM 0x04
%define OM 0x08
%define UM 0x10
%define PM 0x20

; for SSE2/SSE3
%define EIM (1<<7)
%define EDM (1<<8)
%define EZM (1<<9)
%define EOM (1<<10)
%define EUM (1<<11)
%define EPM (1<<12)

    segment_text

proc func
    ; FPUの例外マスク設定
    ; get cw
    push    eax
    fstcw   word [esp]
    pop     eax

    and     eax, ~(IM | DM | ZM |OM |UM | PM)

    ; set cw
    push    eax
    fldcw   word [esp] ; (1)
    pop     eax

    ; calc

    fldz
    fldz
    fdivp   st1 ; (2)
    faddp   st0 ; (3)

    ; SSE2の例外マスク設定
    ; get mxcsr
    push    eax
    stmxcsr [esp]
    and     dword [esp], ~(EIM | EDM | EZM | EOM | EUM | EPM)
    ldmxcsr [esp] ; (4)
    pop     eax

    pxor    xm0, xm0
    divps   xm0, xm0 ; (5)

    ret
上記において(1)で例外を発生させるようにしただけでは(5)の0/0の例外は発生しない. (4)の様に設定すれば発生する.
ところで今知ったのだけどFPUでの割り算は実行したとき(2)ではなく, そのレジスタを参照(or 演算?)したとき(3)に例外が発生するようだ. 試しに(3)をコメントアウトすると何も起こらない. ちなみにSSE2では実行したとき(5)に例外が発生した. このへん, まじめにドキュメント読まないと間違った解釈してるかも.
1st:2006/11/20, last update:2017/04/25

御意見は光成滋生<herumi@nifty.com>までお願いします