「 データ解析 」一覧

BC7について段階的にデコードしてみた


BC7のCPUデコーダーを作っている過程でおもしろいものが確認できたので記事にしてみました。当たり前の話ではあるのですが、視覚化されたケースって無いようなので。
BC7 はいわゆる第2世代のテクスチャ圧縮技術で、各ブロックごとに最適なモードを選択してデータを圧縮しています。このブロックがどんな風に割り当てられているかを、ブロックごとの色分けで塗ってみたら以下のような結果を得られました。

image_partition

この段階でも各ブロックの特徴によってモード選択されているのが確認できます。そのためおおよその画像の検討が付く程度にはなっています。これらのブロック種別を順番に展開してみます。 続きを読む


ASTCの圧縮ノイズを調べてみた


今回は期待のASTC圧縮のノイズを調べてみました。

ASTCについて

詳しいことは各所のサイトで触れられていますのでここでは簡単に。 Adaptive Scalable Texture Compression の略で、ARM が開発しました。DXTCがS3の特許関係で色々とあったためか、 ASTC はロイヤリティフリーなことも取り上げられています。
基本的には他の現世代のテクスチャ圧縮と同じくブロックごとのモードを多数持っていることが特徴です。ASTC 固有の特徴は、なんといってもブロックサイズの変更ができるということではないかと思います。

実験

ASTCではブロックサイズを変更することができ、許容できる範囲で BPP を落として削減することができます。試行錯誤の余地があるといえます。
ここでは BC7 や PVRTC への対抗と考えて、それと同じレベルの BPP でチェックをしてみたいと思います。

圧縮のためのツールとしては Mali Texture Compression Tool を用いています。圧縮品質としては最大の品質レベルを選択してデータを作成しました。

ASTC 8BPP(4×4)での結果

8BPPとなるように 4×4 ブロックで圧縮を行いました。この場合は以下のような結果となりました。次の規格レベルだけあって予想通り綺麗に圧縮できていると思われます。左右に並べたくらいではちょっとわかりません。

compare_std_astc_sample01

compare_std_astc_sample02

ツールのほうでは圧縮前後でノイズ具合も表示できるのでこちらの画像も掲載しておきます。
ノイズは割と控えめな感じかと思われます。 [続きを読む]


ETC2の圧縮ノイズを調べてみた


今回は OpenGL ES 3.0 以降で使えるようになった ETC2 について試してみました。アルファチャンネル入りでの圧縮形式として標準的に使えるようになった形式です。基本的な実装方針は ETC1 の拡張したイメージになっています。

データの作成は Mali Texture Compression Tool を用いて行っています。品質は最高品質を選んでいますが、待てないほどではないにしろ割と時間がかかるかなという印象です。その分画質はどうなっているか確認してみたいと思います。

左がオリジナル、右が ETC2 圧縮したものですがどうでしょうか。違いはぱっと見た感じでわかるでしょうか?

compare-etc2-sample01
compare-etc2-sample02

個人的には ETC1 のころと違ってよく綺麗に圧縮できているのではないかと思っています。そこで今度はこれらの一部分を拡大して確認してみたいと思います。 続きを読む


PVRTC2の圧縮ノイズを調べてみた


今回は PVRTC2 の圧縮について、どのくらいの劣化が起こるのかを調べてみました。 PVRTC2 って有名なようでイマイチな感じで、これを検索キーワードにしても PVRTC1 の 2BPP モードがヒットする感じです。 PVRTC-I, PVRTC-II という表記の方がいいのかもしれませんが、 imgtec のブログでは PVRTC, PVRTC2 という記述をされているようです。

PVRTC2について

PVRTC2 は PVRTC をさらに拡張した形式となっています。基本的なブロックフォーマットはそのままに、ブロックごとの2色を記している最上位ビットに別の意味を持たせた感じにしています。それぞれに Opaque なビットは不要、他方から判定すればよし!というのは納得がいきます。
この別の意味を付与されたビットを HardBit と呼んでいます。これにより最近流行のブロックのモードを追加するということを実現しており、追加されたモードは Non-interpolated(非補間), Local palette(ローカルパレット) モードの2種類となっています。これらの仕組みから PVRTC2 は画質の向上のために用意されているようです。

参考: PVRTC,PVRTC2についての Imagination Technologies のブログ

実験

以前の BC7 のノイズ検証でも使ったデータを利用して確認してみます。PVRTC2 データの作成ついては、今のところ PVRTexTool で行えます。
このツールは前後でのノイズ(エラー)も表示させることが可能なため、以前のように別アプリで処理しなくても簡単に確認できます。ここではそのスクリーンショットを載せることで見ていきたいと思います。
注意事項としては、 BC7 と PVRTC2 を単純に比較してはいけないということです。お気づきかと思いますが、 BC7 は 8bit per pixel(BPP) であり、 PVRTC2 は 4BPP であるということです。つまり PVRTC2 が BC7 よりも2倍圧縮率が高いということで、すなわち BC7 より見た目が悪くても当然ということです。

pvrtc2-noise-sample01

pvrtc2-noise-sample02

よく言われていることではありますが、PVRTC系はアルファチャンネルも持たせられるがその場合カラーの品質が落ちる、というのが今回も健在のようです。最初の例ではアルファ抜きの部分にもノイズがありますし、アルファが変化するブロックにあるカラーブロックがひどいノイズとなっています。
アルファ不要のケースにおいてはノイズは階調が変わる輪郭部分に大きく発生していますが、等倍の目視ではあまり感じられないかと思います。 続きを読む


BC7の圧縮ノイズについて調べてみた


優秀だという BC7 圧縮の品質がどんなものか調べてみました。
そのためにはそこそこの品質以上の画像データが必要だったので友人に協力してもらい画像を使わせてもらっています。
この場を借りてお礼申し上げます。

BC7への圧縮については Codeplex からダウンロードできる Microsoft の TexConv を使用しています。この最高品質となる CPU で処理した結果ではなく、 GPU を用いた速度とのバランスがとれているであろうモードを選択しています。

比較

いきなりですが圧縮前後の画像を比較してみます。
左が圧縮前、右が圧縮後のデータです。圧縮ノイズなどは確認できるでしょうか?自分では等倍でぱっと見た感じわかりませんでした。心持ち髪の毛曲線部とアルファの部分でノイズが気になるかも程度です。
compare_std_bc7_sample01
compare_std_bc7_sample02

続きを読む


Mach-Oのオブジェクトファイルの関数を呼び出す


いわゆるBinary Hacksにあった、「オブジェクトファイルを自力でロードする」のMach-O版をやってみました。MacではオブジェクトファイルもまたMach-O形式で出力されています。

今までの記事の内容で多くのことがわかってきました。特に関数フックが実現出来るようになった今、形式が同じであるオブジェクトファイルのロード&実行はそんなに難易度の高いものではなさそうです。

Mach-Oオブジェクトのロード

まずは簡単にやってみようとおもいます。ここでは以下のデータが必要になってきます。

・symtab_command
・シンボルテーブル
・ストリングテーブル

これらの情報から、すでに関数名から関数実体を検索することが(実は)可能です。知っての通り nlist_64のメンバ n_strx がストリングテーブルのオフセットを示しているので、シンボルテーブル全てのnlistを辿って、名前一致で検索すれば関数が存在していれば発見することが出来ます。
このときの n_value が、そのシンボルが存在するセクションの先頭からのオフセットとなっています。実際には、ロードしたオブジェクトファイル先頭+該当セクションのoffsetメンバ+n_value で関数の所在が計算できます。

求まった場所でそのまま実行できるか、というとメモリ保護の問題があるので mprotectで実行属性を付与します。実行属性さえつけてしまったら、多くの場合はこれで実行が可能です。

ここまでの内容を擬似コードで示すと以下のようになるかなと思います。

リロケーションについて

多くの場合は上記の方法でも問題なく動くことだと思います。ただたまにリロケーションが必要だったりするのでこの場合はちょっと厄介です。一般的なケースではグローバル変数にアクセスしている場合が該当してしまうようです。

リロケーションの情報は __textセクションの reloff メンバで示される位置に、nreloc個の情報が格納されています。これが struct relocation_info の配列となっています。この配列メンバを地道に解釈して、必要な位置の値を書き換えていくことでリロケーションが完成するようです。

多くの場合PC相対アドレスをジャンプ先として埋め込んでいくとか変数参照先としているような感じです。そのため、はめ込み元とアクセス先のアドレスを計算して差分を埋め込む、という形になります。


Mach-Oでの関数フック


ようやくここまでたどり着きました。以前 elfバイナリでやったことのMach-O版です。
実行体は外部.soの何かの関数を呼び出しているとして、その関数への参照をフックしたいという要求に応えてみたいと思います。
フックした後の関数は実行体内部に存在するとします。

これらの関係を図示すると以下のようになります(図は以前の使い回しです…)。

elf-hook1

今までに調査してわかっている内容を利用するとこれが実現出来ます。

前提条件にあるように外部の共有ライブラリを利用する場合、インポート関数テーブルが生成されます。このテーブルが参照するデータの中に関数の実体が格納される部分がありました。このあたりの調査過程はこちらの記事を参考にしてみてください。
この la_symbol_ptrセクションの該当する部分を求めて、置き換えたい関数のアドレスに更新するだけで処理は完了です。
実は、jump_tableだとか __IMPORTというセクションも存在するらしいのですが、手元ではこれらの実行体を作ることが出来ず未確認です。

準備

ロードコマンド LC_SYMTAB からシンボルテーブルの位置を求める
ロードコマンド LC_STRTAB からストリングテーブルの位置を求める
ロードコマンド LC_DYSYMTAB から動的シンボルテーブルの情報を取得する
セクションの情報を処理して、la_symbol_ptrのセクション情報を取得する

フック仕込み

関数名から関数のアドレスが書かれている場所を求めるまでは以下のようになります。

シンボルテーブルの中身(nlist構造体の配列)の中から関数名情報を求める。
関数名がフックを仕込みたい関数名かどうかをチェック。
次にその関数が Indirectテーブルの中でどの位置にあるかを検索
上記で求まった序数で、インポートテーブルの場所を特定し、その中に新しい関数のアドレスを書き込んでおく。
ここでのポイントは序数はあくまでインポートテーブルでの序数とするため、reserved1で示される開始インデックス値を引いて求める必要があります。

インダイレクトテーブルに関しては以前の記事にて走査の方法が使えるのでここでは割愛します。


Mach-O編 Import関数の列挙


前回の最後で dysymの undef extsymbolのほうがImport関数の情報として正しそうと感じていましたが、どうやらそれは間違っていたようです。正しくはIndirectテーブルから求めていくのが正解のようです。

このIndirectテーブルは struct dysymtab_command の indirectsymoff で示される場所に配置されています。
そしてこのテーブルのエントリ数は nindirectsyms となっています。

ではこのエントリ数分のnindirectsymsがインポート関数かと言われるとそうでもないようで、この中の一部分となっているようです。
セクション __la_symbol_ptr で示される reserved1メンバがこのIndirectテーブルでの開始点を示すことになっているようです。
そこで、エントリのreserved1から nindirectsymsまでの中身を確認してみます。

このエントリは uint32_t の配列となっているので取り出すと単なる整数です。
これはシンボルテーブルのエントリのインデックスとなっています。情報表示をするにはシンボルテーブルの情報にアクセスする必要があります。

これらの処理を以下のようにコードにしてみました。割と自明なところのコードは省きます。

この実行結果の一部を公開するとこんな感じになります。

  256 : __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEE6sentryD1Ev
  257 : __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEi
  258 : __ZNSt3__113basic_ostreamIcNS_11char_traitsIcEEElsEm
  261 : __ZNSt3__16localeD1Ev
  262 : __ZNSt3__18ios_base33__set_badbit_and_consider_rethrowEv
  263 : __ZNSt3__18ios_base5clearEj
  264 : __ZSt9terminatev
  265 : __ZdlPv
  266 : __Znwm
  268 : ___cxa_begin_catch
  269 : ___cxa_end_catch
  245 : __Unwind_Resume
  267 : ___cxa_atexit
  271 : ___stack_chk_fail
  273 : __dyld_get_image_header
  274 : __dyld_get_image_name
  275 : __dyld_get_image_vmaddr_slide
  276 : __dyld_image_count
  277 : _getpid
  278 : _mach_port_deallocate
  280 : _mach_vm_region
  281 : _printf
  282 : _strcmp
  283 : _strlen
  284 : _sysctl

それっぽいものが出力されるようになりました!


セグメント&セクションの補足と外部関数への依存について


mach-o編もずいぶんと進んできたので、PEの時にやったように依存するライブラリ情報だけでなく、関数名を表示したいを思って色々を調べてみました。その結果、条件限定かもしれませんがうまく表示できるようになりました。今回はその内容をメモとして公開しようと思います。正しい方法とか指摘してくれると幸いです。

セクションについて

今までのセクション情報で表示できていない部分(わかっていなかった部分)がありました。それは __stubs, __nl_symbol_ptr, __la_symbol_ptr といった一部のセクションでは reserve1, reserve2のメンバに有効な値が入っているということでした。たとえば、 __stubs セクションでは reserve1に インダイレクトシンボルのインデックス開始点が格納され、reserve2にスタブ1要素のバイト数が格納されていました。他の場合でも reserve1にインダイレクトシンボルの開始点が格納されていたりします。

主に動的ライブラリ用の情報を主に調べていくと、情報として必要になるのは symtab, dysymtabのロードコマンドの情報と、__stubs, __nl_symbol_ptr, __la_symbol_ptr のセクション、そして各シンボルテーブルといったところです。含まれているシンボルを一覧する際には symtabの中身を表示すればよい程度でしたが、動的ライブラリの中に含まれる関数(&シンボル)を使っている場合、ここまで簡単なものではなかったです。

__stubsについて

ここの中身は名前の通り外への関数のスタブです。elfバイナリでいうところの PLTっぽい印象を受けました。とりあえずここに含まれる関数の情報を出力するとそのバイナリが使用している外部関数の情報が一覧として出力できそうです。
 この関数情報を辿る場合には、まず reserve1で開始インデックス点を取得し、サイズと1要素の関係から含まれる個数を求めます。その後インダイレクトテーブルからインデックスを取得します。このインデックス値がシンボルテーブルのインデックス値となるようなので、シンボルの文字列テーブルからシンボル名を取得することができるようになります。

__nl_symbol_ptrセクション

このセクションのreserve1にはインダイレクトテーブル内での開始インデックス値が格納されています。そこから参照するするインデックス値がテーブル内から求まるので、シンボルテーブルにアクセスします。注意点としては、インダイレクトテーブルからテーブル引きした値が特定の値かどうかチェックする必要があるということです。ある値ではシンボルテーブルへアクセスすると範囲外アクセスとなってしまいます(INDIRECT_SYMBOL_LOCAL, INDIRECT_SYMBOL_ABS)。

__la_symbol_ptrセクション

このセクションもまた __nl_symbol_ptrセクションと同じように処理をします。

アクセスのコードとか

これらの情報にアクセスしている部分のコード抜粋(というか擬似コード?)を掲載します。

このコードにより今まで使用しているサンプルプログラム(C)の実行体を表示させてみると以下のようになります。オフセットや中身のデータは正しそうですし、一応関数名(シンボル)もまた表示されているのでうまくいっていそうです。

nl_symbol_ptr
  offset:00001000  data: 0000000000000000  (dyld_stub_binder)
  offset:00001008  data: 0000000000000000  (ABSOLUTE)
la_symbol_ptr
  offset:00001010  data: 0000000100000F54  (_printf)
*** import func(?) ***
0 : _printf (100000f3e)

もうすこし複雑なプログラムにしてみようと思います。以下に示すようにCの標準関数とC++の関数を使用してみました。これならば依存ライブラリは2つになりますので、さっきよりは多くの情報が出力されるのが期待できます。

これに対する実行結果は以下のようになりました。

nl_symbol_ptr
  offset:00001000  data: 0000000000000000  (dyld_stub_binder)
  offset:00001008  data: 0000000000000000  (ABSOLUTE)
la_symbol_ptr
  offset:00001028  data: 0000000100000ECC  (__ZNSolsEPFRSoS_E)
  offset:00001030  data: 0000000100000ED6  (__ZNSt8ios_base4InitC1Ev)
  offset:00001038  data: 0000000100000EE0  (__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc)
  offset:00001040  data: 0000000100000EEA  (___cxa_atexit)
  offset:00001048  data: 0000000100000EF4  (_printf)

*** import func(?) ***
0 : __ZNSolsEPFRSoS_E (100000e9c)
1 : __ZNSt8ios_base4InitC1Ev (100000ea2)
2 : __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc (100000ea8)
3 : ___cxa_atexit (100000eae)
4 : _printf (100000eb4)

C++のマングルが入っています。明らかにC++のランタイムへの参照が見て取れます。また先ほどと同様のC関数もまた含まれています。importの項目は依存している関数や変数の情報だけを表示しているようにも見え、__stubsの情報が今求めているImport関数一覧と近いものという考えが合っているように思います。

ただ実際のところ、dysymの情報にある undef extsymbol の情報を表示させてみると以下のようになり、__stubsによる一覧よりも多いことがわかります。より正確にはこちらの情報の方がImportの一覧として正しい気もしてきます。

*** undef extsymbol ***
0 : __ZNSolsEPFRSoS_E
1 : __ZNSt8ios_base4InitC1Ev
2 : __ZNSt8ios_base4InitD1Ev
3 : __ZSt4cout
4 : __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
5 : __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
6 : ___cxa_atexit
7 : _printf
8 : dyld_stub_binder

mach-oの外部関数解決の仕組みを追う(__stubs, __nl_symbol_ptr, __la_symbol_ptrセクション)


C/C++ミックスの実行体を使うようにしたら、実は __la_symbol_ptr セクションは存在しつつ、 __got セクションが出現しました。__la_symbol_ptrセクションが示す先が PLT.GOT相当の領域っぽいなぁと思っていただけに、そのものズバリなセクションが出現して驚きでした。この__gotセクションも他の同種セクションのように扱うだけで参照先を特定することができます。

今回は、こういった外部関数の呼び出しにおいてどのように解決処理がなされるのかを追いかけてみたいと思います。

用意するもの

上記のような簡単な Cプログラムを用意して確認してみます。
このプログラムで、1回目の printf で関数初回呼び出し、2回目で解決済み呼び出しとなるのか、を調査しようという計画です。

調査

まずは実行体の調査をしておきます。このプログラムでは __stubs の内容が以下のようになっていました(以前のプログラムを実行させて必要そうなデータだけ抜粋します)。

section[1] : __TEXT [__stubs]
           : addr=0x100000f3e, offset=3902, size=6
           : align=2, reloff=0, nreloc=0
           : resv0=0, resv1=6, resv2=0
           : flags=80000408 > S_SYMBOL_STUBS S_ATTR_PURE_INSTRUCTIONS S_ATTR_SOME_INSTRUCTIONS
           : Indirect Sym Index = 0, Size of Stubs = 6 (stub count=1)
section[2] : __TEXT [__stub_helper]
           : addr=0x100000f44, offset=3908, size=26
           : align=4, reloff=0, nreloc=0
           : resv0=0, resv1=0, resv2=0
           : flags=80000400 > S_REGULAR S_ATTR_PURE_INSTRUCTIONS S_ATTR_SOME_INSTRUCTIONS

section[0] : __DATA [__nl_symbol_ptr]
           : addr=0x100001000, offset=4096, size=16
           : align=8, reloff=0, nreloc=0
           : resv0=1, resv1=0, resv2=0
           : flags=00000006 > S_NON_LAZY_SYMBOL_POINTERS
           : Indirect Sym Index = 1
section[1] : __DATA [__la_symbol_ptr]
           : addr=0x100001010, offset=4112, size=8
           : align=8, reloff=0, nreloc=0
           : resv0=3, resv1=0, resv2=0
           : flags=00000007 > S_LAZY_SYMBOL_POINTERS
           : Indirect Sym Index = 3

nl_symbol_ptr
  offset:00001000  data: 0000000000000000  (dyld_stub_binder)
  offset:00001008  data: 0000000000000000  (ABSOLUTE)
la_symbol_ptr
  offset:00001010  data: 0000000100000F54  (_printf)
*** import ***
0 : _printf (100000f3e)

main関数直後でbreakさせて、コードを確認してみます。

(lldb) disassemble
a.outmain:
-> 0x100000f00:  pushq  %rbp
   0x100000f01:  movq   %rsp, %rbp
   0x100000f04:  subq   $0x10, %rsp
   0x100000f08:  leaq   0x4f(%rip), %rdi          ; "Hello,"
   0x100000f0f:  movl   $0x0, -0x4(%rbp)
   0x100000f16:  movb   $0x0, %al
   0x100000f18:  callq  0x100000f3e               ; symbol stub for: printf
   0x100000f1d:  leaq   0x41(%rip), %rdi          ; "world.\n"
   0x100000f24:  movl   %eax, -0x8(%rbp)
   0x100000f27:  movb   $0x0, %al
   0x100000f29:  callq  0x100000f3e               ; symbol stub for: printf
   0x100000f2e:  movl   $0x0, %ecx
   0x100000f33:  movl   %eax, -0xc(%rbp)
   0x100000f36:  movl   %ecx, %eax
   0x100000f38:  addq   $0x10, %rsp
   0x100000f3c:  popq   %rbp
   0x100000f3d:  ret    

printfの関数として 0x100000f3e アドレスを呼び出しています。この部分は事前情報によれば、__stubsの内容の先頭アドレスと一致しています。まさにprintfのスタブですね。
この 0x100000f3e アドレスでは以下のようになっていました。この周辺をちょっと調べてみます。

(lldb) disassemble 
a.outsymbol stub for: printf:
-> 0x100000f3e:  jmpq   *0xcc(%rip)               ; (void *)0x0000000100000f54

(lldb) memory read 0x100000f44+0xcc   ; 上記のrip+相対0xccで求まる番地
0x100001010: 54 0f 00 00 01 00 00 00 00 00 00 00 00 00 00 00  T...............
0x100001020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

(lldb) si
* thread #1: tid = 0x25c01, 0x0000000100000f54 a.out, queue = 'com.apple.main-thread', stop reason = instruction step into
    frame #0: 0x0000000100000f54 a.out
-> 0x100000f54:  pushq  $0x0
   0x100000f59:  jmpq   0x100000f44

(lldb) b 0x100000f29
(lldb) c

Process 1698 stopped
* thread #1: tid = 0x25c01, 0x0000000100000f29 a.outmain + 41, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x0000000100000f29 a.outmain + 41
a.outmain + 41:
-> 0x100000f29:  callq  0x100000f3e               ; symbol stub for: printf
   0x100000f2e:  movl   $0x0, %ecx

(lldb) si
Process 1698 stopped
* thread #1: tid = 0x25c01, 0x0000000100000f3e a.outprintf, queue = 'com.apple.main-thread', stop reason = instruction step into
    frame #0: 0x0000000100000f3e a.outprintf
a.outsymbol stub for: printf:
-> 0x100000f3e:  jmpq   *0xcc(%rip)               ; (void *)0x00007fff966e3784: printf
   0x100000f44:  leaq   0xbd(%rip), %r11          ; (void *)0x00007fff5fc3b7b8

(lldb) memory read 0x100001010
0x100001010: 84 37 6e 96 ff 7f 00 00 00 00 00 00 00 00 00 00  .7n.?...........
0x100001020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

(lldb) disassemble --start-address 0x7fff93021878
libdyld.dylib`dyld_stub_binder:
   0x7fff93021878:  pushq  %rbp
   0x7fff93021879:  movq   %rsp, %rbp
   0x7fff9302187c:  subq   $0xc0, %rsp
   0x7fff93021883:  movq   %rdi, (%rsp)
   0x7fff93021887:  movq   %rsi, 0x8(%rsp)

わかる人には不要な話ですが、とりあえず解説をしてみます。
jmpq 命令では指定されたアドレスの内容へ飛んでいます。またこのアセンブラを見るとRIPとオフセット値(0xCC)でアドレスを求めています。そのアドレスの中身を見てみると、元々のコメントで指摘あるように 0x100000f54 が書かれていました。
このアドレスが 0x100001010 となります。このアドレスというのが la_symbol_ptrセクションの内容(先頭)となっています。
0x100000f54番地付近では 0x10000f44へジャンプするコードが見えます。この 0x10000f44 というアドレスは、__stub_helper の内容です。ここで __stub_helperの実行が行われることがわかります。
__stub_helperのやっていることはちょっと難しいので飛ばして、2回目のprintfの実行まで飛ばします。同じように 0x100000f3eのコードを見てみるとコメント部分が変わっていることに気付きます。先ほどと同じように 0x100001010 のアドレスの中身を見てみます。前回見たときの値と違う値が格納されていることがわかります。動作が同じなので今回はこの値を使って関数を呼び出していることになります。このアドレスは何かというとこれが printf関数実体です。

__stub_helperの実行後は何らかの関数アドレスが更新され、アドレス解決されることがわかります。この元々何かのアドレス格納されているが実行後更新されて格納される場所がまさにPLT.GOTっぽいなーと思います。