Skip to content
hiromi-mi edited this page Sep 26, 2020 · 3 revisions

about hh4

実行バイナリの即値ヒストグラム リンクフリーですが、ここにある資料は予告なく移動削除されることがあります。

hh4 の内部構造決定に定数の分布をみたらどうか、とのhikalium さんの意見に勝手に呼応して調べてみている. 本節の内容は CC0-1.0 とします.

はじまり

定数出現頻度について、即値だけならad-hoc に収集できそうだったのでワンライナーを書いた.

$ objdump --disassemble {filenames} | grep "\$0x[[:xdigit:]]*" --only-matching | cut --characters="2-" > log.log

逆アセンブル結果中の即値が 0x34 などと抽出されるので,

$ sort log.log | uniq --count | sort --reverse

こうしてみると即値ごとの出現回数ランキング上位から見られます. wc --line logs.log で全体の即値件数が分かるので, それらから割合も分かる.

課題:

  1. 現状の処理では負の数も全部2の補数表現で集計されているものの、byte なのかdword なのかを知らないまま処理しているので負の数を -1 などと集計するのができない (追記: mov 先のレジスタを見れば何byte を期待しているかは一応分かるものの、もはや上のようなテキスト処理では調べられなくなって手間がかかる)

パッケージ全体での傾向を見る

目的: パッケージごとよく使われる定数値を知りたい。それらにばらつきがあるかを調べたい。

私案: 手元PC にあるリバースエンジニアリングが禁止されていない自由ソフトウエアについて、各パッケージごとに実行バイナリと共有ライブラリと静的ライブラリに分けて処理をする. するとパッケージごとバイナリの種類ごとによく使われる literal ランキングが分かるので、全体として上位にあるliteral と、そのhh4での型を抜き出す.

Arch Linux での実装

for packname in $( expac -Q '%n\t%L'  | grep -v " \?custom" | awk '{ print $1}'); do objdump -d $(pacman -Qlq $packname) | grep "\$-\?0x[[:xdigit:]]\+" -o | cut -c "2-"> ${packname}_raw.log; sort ${packname}_raw.log | uniq -c | sort -r -n > ${packname}.csv; done

問題点: 実行バイナリと共有ライブラリが混ざっている

3種類ほどデータベースを作ってみた

例1 (2020.9.24)

私案1 を実行に移した. Arch Linuxにおいて 手元にある (a52dec から celt までのバイナリファイルがある自由ソフトウエア) 52パッケージに対してそれぞれ処理を行い, 上位10位までを抜き出した.

head -n 10 -q *.csv  | awk '{print $2}' | sort | uniq -c | sort

結果

      1 0x100
      1 0x1000
      1 0x11
      1 0x12
      1 0x14
      1 0x16
      1 0x2000
      1 0x2710
      1 0x2a
      1 0x2f
      1 0x38
      1 0x3d
      1 0x3f
      1 0x40
      1 0x4000
      1 0x431bde82d7b634db
      1 0x47
      1 0x48
      1 0x49
      1 0x5306
      1 0x5f
      1 0x65
      1 0x69
      1 0x7fff
      1 0x80
      1 0x9
      1 0x930
      1 0x96
      1 0xe
      1 0xf0
      1 0xf4240
      1 0xfe00fe00
      1 0xff
      1 0xff00ff00
      1 0xffff
      1 0xffffff80
      1 0xffffffb5
      1 0xfffffffe
      1 0xffffffff00000000
      2 0x73
      2 0xffffffea
      2 0xfffffffffffffff0
      3 0x1f
      4 0x50
      4 0x6
      4 0x7
      4 0xc
      4 0xf
      5 0x28
      5 0x30
      9 0xa
      9 0xffffffffffffffff
     14 0x5
     27 0x18
     28 0x20
     33 0xffffffff
     34 0x10
     42 0x3
     43 0x4
     49 0x2
     51 0x0
     51 0x8
     52 0x1

パッケージごとの傾向が似通っているのでは?

例2

H8300-GCC-hh4.md 参照

例3

hh4a lv2 では頻度の高いデータを特別扱いしている。それらが実際に頻出か調べた。 全体で 2182179 件あることに留意、 データベースの生データは encoding-db-data.md 参照

出現回数 即値
2793	0x200
725	0x3ff
4773	0x400
216	0x7ff
1684	0x800
878	0xfff
3685	0x1000
246	0x1fff
1385	0x2000
149	0x3fff
1340	0x4000
621	0x7fff
1826	0x8000
4093	0xffff
1756	0x10000

0x1fff 0x3fff あたりは後回しでもよいかもしれない

例4

全体で 2182179 件あったうち、2000件以上のアイテムに関してどの型が優先的に出てくるか調べた

データベースの生データは encoding-db-data.md 参照

出現回数 値 hh4 で表わしたときのビット数
380094	0x1 4
349622	0x0 4
161729	0x8 8
106863	0x2 4
70805	0x4 4
69480	0x3 4
57443	0x10   8
42789	0x5 4
42072	0x18   8
40679	0x20   8
37996	0xffffffff   40+8?
26322	0xffffffffffffffff ???
25814	0x7 8
22708	0x28   8
22167	0x6 4
18071	0x40   12
17814	0xa 8
17781	0xf 8
16730	0x30   8
14177	0x1f   8
13744	0x38   8
13012	0xc 8
11195	0x9 8
10422	0x48   12
9551	0x3f   8
9499	0x80   12
9401	0xb 8
7000	0xd 8
6858	0xff   12
6762	0x50   12
6672	0x58   12
6566	0x11   8
5984	0xe 8
5958	0x68   12
5464	0x100  12
5140	0x2f   8
5025	0xfffffffe   40+8?
4851	0x14   8
4773	0x400  16
4660	0x12   8
4114	0x78   12
4093	0xffff 24
4025	0x19   8
3868	0x13   8
3860	0x15   8
3721	0x60   12
3685	0x1000 24
3643	0x17   8
3619	0x16   8
3448	0x1c   8
3359	0xfffffffffffffff8 ???
3261	0xa8   8
3214	0x7f   12
3201	0x2b   8
3197	0xfffffffc 40+8?
3157	0x2e   8
3093	0x88   12
3024	0x7fffffff   40+8?
3020	0x1e   8
3010	0xffffffea   40+8?
3001	0x2d   8
2954	0xd8   12
2895	0xfffffff4   40+8?
2876	0xfffffffd   40+8?
2843	0x2a   8
2827	0x98   12
2793	0x200  12
2744	0x64   12
2699	0x1d   8
2670	0x22   8
2650	0x65   12
2605	0x70   12
2556	0xc8   12
2547	0x29   8
2430	0x1a   12
2313	0x24   8
2262	0xb8   12
2259	0xfffffffb   40+8?
2063	0x21   8
2059	0x3a   12
2040	0x2c   8
2034	0x73   12

Encoding Criteria

// SPDX-License-Identifier: CC0-1.0

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int test(void);
// value (v) は hh4 で何bit で表わされるか?
// TODO: 今のところ unsigned long long のみ対応
long long hh4_num2bits(unsigned long long value) {
   if (value <= 6) {
      return 4; 
   }
   if (value <= 0x3f) {
      return 8;
   }
   if (value <= 0x1ff) {
      return 12;
   }
   if (value <= 0xfff) {
      return 16;
   }

   // extended
   long long bits = (long long)(log2l(value)/4) + 1;
   return 4 + hh4_num2bits(bits) + bits*4;
}

#define expect(a, b) if ((a) != (b)) { fprintf(stderr, "Test Failed: %Ld, %Ld\n", (a), (b)); }

int main(int argc, const char **argv) {
   if (argc < 2) {
      printf("Usage: %s filename\n", argv[0]);
      exit(1);
   }

   FILE *fp = fopen(argv[1], "r");
   if (fp == NULL) {
      puts("fopen failed");
      exit(1);
   }

   long long sum = 0;

   test();

   puts("出現数\t即値\tbit数\t出現数*bit数");
   while (!feof(fp)) {
      long long cnt = 0; // 出現回数
      unsigned long long val = 0; // 即値
      long long contribution = 0; // 出現回数 * hh4_num2bits(val)
      fscanf(fp, "%Ld\t%Lx\n", &cnt, &val);
      contribution = cnt * hh4_num2bits(val);
      sum += contribution;

      // それぞれの即値の結果を見たいときにコメントアウト
      // printf("%Ld\t0x%Lx\t%Ld\t%Ld\n", cnt, val, hh4_num2bits(val), contribution);
   }
   printf("sum: %Ld bits\n", sum);
   fclose(fp);
   return 0;
}

// テスト
int test(void) {
   expect(4, hh4_num2bits(6))
   expect(8, hh4_num2bits(7))
   expect(8, hh4_num2bits(0x3f))
   expect(12, hh4_num2bits(0x40))
   expect(12, hh4_num2bits(0x1ff))
   expect(16, hh4_num2bits(0x200))
   expect(16, hh4_num2bits(0xfff))
   expect(24, hh4_num2bits(0x1000))
   expect(24, hh4_num2bits(0xffff))
   expect(28, hh4_num2bits(0x10000))
   expect(28, hh4_num2bits(0xfffff))
   expect(32, hh4_num2bits(0x100000))
   expect(32, hh4_num2bits(0xffffff))
   return 0;
}

これでおそらくエンコーディング方式の効率性を定量的に比較できるようになる (無論簡単な分布であればそれをtable にmap してしまうのが一番効率よくなるが, そうではなくある程度 "実装が簡単で拡張性がある" 中でこの値が小さいほうがいいかなと)

  • 入力1: {定数分布 as 上の実験で書いたファイルとして定数分布を格納} をコマンドライン引数
  • 入力2: エンコーディングフォーマットにおいて 値 → 使用する bit数 の関係 hh4_num2bits()
  • 出力: 定数分布を全て表すのに必要なbit数の総和

hh4a lv1 での実装

(再掲)

// SPDX-License-Identifier: CC0-1.0
long long hh4_num2bits(unsigned long long value) {
   if (value <= 6) {
      return 4;
   }
   if (value <= 0x3f) {
      return 8;
   }
   if (value <= 0x1ff) {
      return 12;
   }
   if (value <= 0xfff) {
      return 16;
   }

   // extended
   long long bits = (long long)(log2l(value)/4) + 1;
   return 4 + hh4_num2bits(bits) + bits*4;
}

拡張モードを一度使うだけで済む範囲なら使う回数は少ないほうが優れている

LEBシリーズでの実装

hh4_num2bits となっている箇所を leb_num2bits に置き換えてコンパイル

// SPDX-License-Identifier: CC0-1.0
long long leb_num2bits(unsigned long long value) {
   const int k = 4;
   unsigned long long v;
   // TODO: 今のところ unsigned long long のみ対応
   v = value;
   if (v == 0) {
      return k+1;
   }
   long long bits = ((long long)(log2l(v)/k)+1)*(k+1);
   return bits;
}

k というパラメータを導入。 [次の1バイトに続くかのflag 1 bit] [内容 k bits] のくりかえし

LEB128 と hh4a lv1 の性能比較

(./leb を実行する際には const int k = 3; の欄を k = 3k = 4 とする)

encoding-db-data.md の5行目最後の1行を除くフィイルを abcdb/integrated.csv に保存して

$ ./hh4 abcdb/integrated.csv
$ ./leb abcdb/integrated.csv

defgdb-integrated.md の5行目以降のデータを defgdb/integrated.csv に保存して

$ ./hh4 defgdb/integrated.csv
$ ./leb defgdb/integrated.csv

libdb-integrated.md の4行目以降のデータを libdb/integrated.csv に保存して

$ ./hh4 libdb/integrated.csv
$ ./leb libdb/integrated.csv

結果(定数を全て表すのに必要なbit数の総和)は以下の通り. 異なる列の値同士は比較できるが、異なる行同士の値は比較できないことに注意。

data hh4a lv1 LEB k=3 LEB k=4
abc 24278356 24449236 24394820
defg 311949820 314540364 313195470
lib 95001436 95218060 95361800

hh4a lv1 を改造する

拡張モードのビット数

l = 4 bit刻みなのを変化させてみた.

data hh4a lv1 l = 4 hh4a lv1 l = 8
abc 24278356 24070196
defg 311949820 309855436
lib 95001436 93815964

l = 8 が最良?

long long hh4_k_num2bits(unsigned long long value) {
   const int l = 8;
   if (value <= 6) {
      return 4; 
   }
   if (value <= 0x3f) {
      return 8;
   }
   if (value <= 0x1ff) {
      return 12;
   }
   if (value <= 0xfff) {
      return 16;
   }

   long long bits = (long long)(log2l(value)/l) + 1;
   return 4 + hh4_num2bits(bits) + bits*l;
}

Clone this wiki locally