| 7 | | <pubDate>Tue, 23 Dec 2008 15:48:14 +0900</pubDate> |
| | 7 | <pubDate>Fri, 26 Dec 2008 15:13:04 +0900</pubDate> |
| | 8 | <item> |
| | 9 | <author>nobody@example.com</author> |
| | 10 | <dc:creator>nobody@example.com</dc:creator> |
| | 11 | <link>http://perl-users.jp/articles/advent-calendar/2008/25.html</link> |
| | 12 | <description>バイナリファイルを解析するPerlといえばテキスト処理や正規表現が得意で、バイナリを扱うような話についてはあまり聞かない印象があります。Perlが持つ関数pack/unpack等でもバイナリ処理は可能ですが、今回はData::ParseBinaryを使ってバイナリファイルを気軽に解析してみましょう。基本ファイルからストリームを作る解析したいファイルをData::ParseBinaryで扱えるストリームに変換します。use Data::ParseBinary; |
| | 13 | my $stream = CreateStreamReader(File => $file_handle); |
| | 14 | 解析したい構造を定義するStruct関数で解析したい構造を定義します。Struct以下には基本データ型やコンテナ型、ビット/バイトパディング型、制御構文型等を使用できます。各型に指定したラベルが解析結果として得られるハッシュのキーとなります。my $your_data_structure = Struct('YOUR_DATA_STRUCTURE', |
| | 15 | UBInt8('length'), |
| | 16 | Array(sub { $_->ctx->{length}}, UBInt8('data')), |
| | 17 | ); |
| | 18 | 定義した構造を元にストリームを解析する解析したい構造に対してストリームを渡して解析を開始します。解析結果はハッシュとして返されます。my $data = $your_data_structure->parse($stream); |
| | 19 | # 結果例 |
| | 20 | # $data = { |
| | 21 | # 'YOUT_DATA_STRUCTURE' => { |
| | 22 | # 'length' => 10, |
| | 23 | # 'data' => [ |
| | 24 | # 1, |
| | 25 | # 2, |
| | 26 | # ..., |
| | 27 | # ], |
| | 28 | # }, |
| | 29 | # } |
| | 30 | 例:FLVファイルを解析する例として、Flashで扱う映像ファイルフォーマットであるFLVファイルを途中まで解析してみます。こちらの仕様書(PDF)を見ながらStruct中を眺めるとやってることがわかると思います。長くなりそうなので途中から詳細はn byteデータチャンクとして扱い、その先を具体的に解析しないようにしています。興味を持たれたらコメントアウト部分を修正して続きを解析してみてください。#!/usr/bin/env perl |
| | 31 | use strict; |
| | 32 | use warnings; |
| | 33 | |
| | 34 | package FLV::Parser; |
| | 35 | use Data::ParseBinary; |
| | 36 | |
| | 37 | sub parse { |
| | 38 | my($self, $file) = @_; |
| | 39 | |
| | 40 | my $s = |
| | 41 | Struct('FLV', |
| | 42 | header(), |
| | 43 | body(), |
| | 44 | ); |
| | 45 | open my $fh, '<', $file; |
| | 46 | binmode $fh; |
| | 47 | my $stream = CreateStreamReader(File => $fh); |
| | 48 | my $data = $s->parse($stream); |
| | 49 | close $fh; |
| | 50 | $data; |
| | 51 | } |
| | 52 | |
| | 53 | sub UBInt24 { |
| | 54 | my($name) = @_; |
| | 55 | Struct($name, |
| | 56 | UBInt8('_b1'), |
| | 57 | UBInt8('_b2'), |
| | 58 | UBInt8('_b3'), |
| | 59 | Value('value', sub { $_->ctx->{_b1} << 16 | $_->ctx->{_b2} << 8 | $_->ctx->{_b3} }), |
| | 60 | ); |
| | 61 | } |
| | 62 | |
| | 63 | sub header { |
| | 64 | # 9 byte |
| | 65 | Struct('Header', |
| | 66 | Const(String('Signature', 3), 'FLV'), |
| | 67 | UBInt8('Version'), |
| | 68 | BitStruct('TypeFlags', |
| | 69 | Padding(5), |
| | 70 | Flag('Audio'), |
| | 71 | Padding(1), |
| | 72 | Flag('Video'), |
| | 73 | ), |
| | 74 | UBInt32('DataOffset') |
| | 75 | ); |
| | 76 | } |
| | 77 | |
| | 78 | sub body { |
| | 79 | Struct('Body', |
| | 80 | UBInt32('PreviousTagSize0'), |
| | 81 | GreedyRange(Struct('tags', |
| | 82 | flvtag(), |
| | 83 | UBInt32('PreviousTagSizeN'), |
| | 84 | )), |
| | 85 | ); |
| | 86 | } |
| | 87 | |
| | 88 | sub flvtag { |
| | 89 | Struct('FLVTAG', |
| | 90 | UBInt8('TagType'), |
| | 91 | UBInt24('DataSize'), |
| | 92 | UBInt24('Timestamp'), |
| | 93 | UBInt8('TimestampExtended'), |
| | 94 | UBInt24('StreamID'), |
| | 95 | Switch("Data", sub {$_->ctx->{TagType}}, { |
| | 96 | 8 => Struct('AUDIODATA', |
| | 97 | BitStruct('info', |
| | 98 | BitField('SoundFormat', 4), |
| | 99 | BitField('SoundRate', 2), |
| | 100 | BitField('SoundSize', 1), |
| | 101 | BitField('SoundType', 1), |
| | 102 | ), |
| | 103 | Array(sub{$_->ctx(1)->{DataSize}->{value} - 1}, UBInt8('SoundData')), |
| | 104 | ), |
| | 105 | 9 => Struct('VIDEODATA', |
| | 106 | BitStruct('info', |
| | 107 | BitField('FrameType', 4), |
| | 108 | BitField('CodecID', 4), |
| | 109 | ), |
| | 110 | Array(sub{$_->ctx(1)->{DataSize}->{value} - 1}, UBInt8('VideoData')) |
| | 111 | ), |
| | 112 | |
| | 113 | # 8 => audiodata(), |
| | 114 | # 9 => videodata(), |
| | 115 | # 10 => scriptdataobject(), |
| | 116 | }, |
| | 117 | default => Array(sub{$_->ctx(0)->{DataSize}->{value}}, UBInt8('VideoData')) |
| | 118 | ) |
| | 119 | ); |
| | 120 | } |
| | 121 | |
| | 122 | |
| | 123 | =head2 commentout |
| | 124 | |
| | 125 | sub audiodata { |
| | 126 | Struct('AUDIODATA', |
| | 127 | BitStruct('info', |
| | 128 | BitField('SoundFormat', 4), |
| | 129 | BitField('SoundRate', 2), |
| | 130 | BitField('SoundSize', 1), |
| | 131 | BitField('SoundType', 1), |
| | 132 | ), |
| | 133 | Switch('SoundData', sub {$_->ctx->{info}->{SoundFormat}}, { |
| | 134 | 10 => aacaudiodata(), |
| | 135 | }) |
| | 136 | ); |
| | 137 | } |
| | 138 | sub aacaudiodata { |
| | 139 | Struct( |
| | 140 | UBInt8('AACPacketType'), |
| | 141 | UBInt8('Data', n) |
| | 142 | ); |
| | 143 | } |
| | 144 | sub videodata { |
| | 145 | Struct('VIDEODATA', |
| | 146 | ); |
| | 147 | } |
| | 148 | sub scriptdataobject { |
| | 149 | Struct('SCRIPTDATAOBJECT', |
| | 150 | ); |
| | 151 | } |
| | 152 | |
| | 153 | =cut |
| | 154 | |
| | 155 | package main; |
| | 156 | use Data::Dumper; |
| | 157 | |
| | 158 | my $data = FLV::Parser->parse('foo.flv'); |
| | 159 | print Dumper $data; |
| | 160 | C言語でバイナリ解析する場合に比べると、細かい構造体単位でstruct定義を分ける必要がなく、複雑な構造であっても見た目に理解しやすいのがいいところです。制御構文のように使える関数もあるので、「bit 31から28の値が0001なら構造A、0010なら構造Bとして次のN Byteを解析する」というような処理も書きやすいです。処理速度は速くないのでリアルタイムで処理するような用途には向きませんが、バイナリエディタで入れ子になったデータ構造のオフセットを確認しながら手動で値を確認するよりわかりやすいでしょう。また仕様書から複雑なデータフォーマットを学ぶ時にも理解の助けとなると思います。Back |
| | 161 | </description> |
| | 162 | <dc:date>2008-12-24T18:14:47Z</dc:date> |
| | 163 | <title>バイナリファイルを解析する</title> |
| | 164 | <pubDate>Wed, 24 Dec 2008 18:14:47 -0000</pubDate> |
| | 165 | <content:encoded><body><h1>バイナリファイルを解析する</h1><p>Perlといえばテキスト処理や正規表現が得意で、バイナリを扱うような話についてはあまり聞かない印象があります。Perlが持つ関数<code>pack</code>/<code>unpack</code>等でもバイナリ処理は可能ですが、今回は<a href="http://search.cpan.org/dist/Data-ParseBinary/">Data::ParseBinary</a>を使ってバイナリファイルを気軽に解析してみましょう。</p><h2>基本</h2><h3>ファイルからストリームを作る</h3><p>解析したいファイルをData::ParseBinaryで扱えるストリームに変換します。</p><pre class="lang-perl"><code>use Data::ParseBinary; |
| | 166 | my $stream = CreateStreamReader(File =&gt; $file_handle); |
| | 167 | </code></pre><h3>解析したい構造を定義する</h3><p>Struct関数で解析したい構造を定義します。Struct以下には基本データ型やコンテナ型、ビット/バイトパディング型、制御構文型等を使用できます。各型に指定したラベルが解析結果として得られるハッシュのキーとなります。</p><pre class="lang-perl"><code>my $your_data_structure = Struct(&apos;YOUR_DATA_STRUCTURE&apos;, |
| | 168 | UBInt8(&apos;length&apos;), |
| | 169 | Array(sub { $_-&gt;ctx-&gt;{length}}, UBInt8(&apos;data&apos;)), |
| | 170 | ); |
| | 171 | </code></pre><h3>定義した構造を元にストリームを解析する</h3><p>解析したい構造に対してストリームを渡して解析を開始します。解析結果はハッシュとして返されます。</p><pre class="lang-perl"><code>my $data = $your_data_structure-&gt;parse($stream); |
| | 172 | # 結果例 |
| | 173 | # $data = { |
| | 174 | # &apos;YOUT_DATA_STRUCTURE&apos; =&gt; { |
| | 175 | # &apos;length&apos; =&gt; 10, |
| | 176 | # &apos;data&apos; =&gt; [ |
| | 177 | # 1, |
| | 178 | # 2, |
| | 179 | # ..., |
| | 180 | # ], |
| | 181 | # }, |
| | 182 | # } |
| | 183 | </code></pre><h2>例:FLVファイルを解析する</h2><p>例として、Flashで扱う映像ファイルフォーマットであるFLVファイルを途中まで解析してみます。<a href="http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v9.pdf">こちらの仕様書(PDF)</a>を見ながらStruct中を眺めるとやってることがわかると思います。</p><p>長くなりそうなので途中から詳細はn byteデータチャンクとして扱い、その先を具体的に解析しないようにしています。興味を持たれたらコメントアウト部分を修正して続きを解析してみてください。</p><pre class="lang-perl"><code>#!/usr/bin/env perl |
| | 184 | use strict; |
| | 185 | use warnings; |
| | 186 | |
| | 187 | package FLV::Parser; |
| | 188 | use Data::ParseBinary; |
| | 189 | |
| | 190 | sub parse { |
| | 191 | my($self, $file) = @_; |
| | 192 | |
| | 193 | my $s = |
| | 194 | Struct(&apos;FLV&apos;, |
| | 195 | header(), |
| | 196 | body(), |
| | 197 | ); |
| | 198 | open my $fh, &apos;&lt;&apos;, $file; |
| | 199 | binmode $fh; |
| | 200 | my $stream = CreateStreamReader(File =&gt; $fh); |
| | 201 | my $data = $s-&gt;parse($stream); |
| | 202 | close $fh; |
| | 203 | $data; |
| | 204 | } |
| | 205 | |
| | 206 | sub UBInt24 { |
| | 207 | my($name) = @_; |
| | 208 | Struct($name, |
| | 209 | UBInt8(&apos;_b1&apos;), |
| | 210 | UBInt8(&apos;_b2&apos;), |
| | 211 | UBInt8(&apos;_b3&apos;), |
| | 212 | Value(&apos;value&apos;, sub { $_-&gt;ctx-&gt;{_b1} &lt;&lt; 16 | $_-&gt;ctx-&gt;{_b2} &lt;&lt; 8 | $_-&gt;ctx-&gt;{_b3} }), |
| | 213 | ); |
| | 214 | } |
| | 215 | |
| | 216 | sub header { |
| | 217 | # 9 byte |
| | 218 | Struct(&apos;Header&apos;, |
| | 219 | Const(String(&apos;Signature&apos;, 3), &apos;FLV&apos;), |
| | 220 | UBInt8(&apos;Version&apos;), |
| | 221 | BitStruct(&apos;TypeFlags&apos;, |
| | 222 | Padding(5), |
| | 223 | Flag(&apos;Audio&apos;), |
| | 224 | Padding(1), |
| | 225 | Flag(&apos;Video&apos;), |
| | 226 | ), |
| | 227 | UBInt32(&apos;DataOffset&apos;) |
| | 228 | ); |
| | 229 | } |
| | 230 | |
| | 231 | sub body { |
| | 232 | Struct(&apos;Body&apos;, |
| | 233 | UBInt32(&apos;PreviousTagSize0&apos;), |
| | 234 | GreedyRange(Struct(&apos;tags&apos;, |
| | 235 | flvtag(), |
| | 236 | UBInt32(&apos;PreviousTagSizeN&apos;), |
| | 237 | )), |
| | 238 | ); |
| | 239 | } |
| | 240 | |
| | 241 | sub flvtag { |
| | 242 | Struct(&apos;FLVTAG&apos;, |
| | 243 | UBInt8(&apos;TagType&apos;), |
| | 244 | UBInt24(&apos;DataSize&apos;), |
| | 245 | UBInt24(&apos;Timestamp&apos;), |
| | 246 | UBInt8(&apos;TimestampExtended&apos;), |
| | 247 | UBInt24(&apos;StreamID&apos;), |
| | 248 | Switch(&quot;Data&quot;, sub {$_-&gt;ctx-&gt;{TagType}}, { |
| | 249 | 8 =&gt; Struct(&apos;AUDIODATA&apos;, |
| | 250 | BitStruct(&apos;info&apos;, |
| | 251 | BitField(&apos;SoundFormat&apos;, 4), |
| | 252 | BitField(&apos;SoundRate&apos;, 2), |
| | 253 | BitField(&apos;SoundSize&apos;, 1), |
| | 254 | BitField(&apos;SoundType&apos;, 1), |
| | 255 | ), |
| | 256 | Array(sub{$_-&gt;ctx(1)-&gt;{DataSize}-&gt;{value} - 1}, UBInt8(&apos;SoundData&apos;)), |
| | 257 | ), |
| | 258 | 9 =&gt; Struct(&apos;VIDEODATA&apos;, |
| | 259 | BitStruct(&apos;info&apos;, |
| | 260 | BitField(&apos;FrameType&apos;, 4), |
| | 261 | BitField(&apos;CodecID&apos;, 4), |
| | 262 | ), |
| | 263 | Array(sub{$_-&gt;ctx(1)-&gt;{DataSize}-&gt;{value} - 1}, UBInt8(&apos;VideoData&apos;)) |
| | 264 | ), |
| | 265 | |
| | 266 | # 8 =&gt; audiodata(), |
| | 267 | # 9 =&gt; videodata(), |
| | 268 | # 10 =&gt; scriptdataobject(), |
| | 269 | }, |
| | 270 | default =&gt; Array(sub{$_-&gt;ctx(0)-&gt;{DataSize}-&gt;{value}}, UBInt8(&apos;VideoData&apos;)) |
| | 271 | ) |
| | 272 | ); |
| | 273 | } |
| | 274 | |
| | 275 | |
| | 276 | =head2 commentout |
| | 277 | |
| | 278 | sub audiodata { |
| | 279 | Struct(&apos;AUDIODATA&apos;, |
| | 280 | BitStruct(&apos;info&apos;, |
| | 281 | BitField(&apos;SoundFormat&apos;, 4), |
| | 282 | BitField(&apos;SoundRate&apos;, 2), |
| | 283 | BitField(&apos;SoundSize&apos;, 1), |
| | 284 | BitField(&apos;SoundType&apos;, 1), |
| | 285 | ), |
| | 286 | Switch(&apos;SoundData&apos;, sub {$_-&gt;ctx-&gt;{info}-&gt;{SoundFormat}}, { |
| | 287 | 10 =&gt; aacaudiodata(), |
| | 288 | }) |
| | 289 | ); |
| | 290 | } |
| | 291 | sub aacaudiodata { |
| | 292 | Struct( |
| | 293 | UBInt8(&apos;AACPacketType&apos;), |
| | 294 | UBInt8(&apos;Data&apos;, n) |
| | 295 | ); |
| | 296 | } |
| | 297 | sub videodata { |
| | 298 | Struct(&apos;VIDEODATA&apos;, |
| | 299 | ); |
| | 300 | } |
| | 301 | sub scriptdataobject { |
| | 302 | Struct(&apos;SCRIPTDATAOBJECT&apos;, |
| | 303 | ); |
| | 304 | } |
| | 305 | |
| | 306 | =cut |
| | 307 | |
| | 308 | package main; |
| | 309 | use Data::Dumper; |
| | 310 | |
| | 311 | my $data = FLV::Parser-&gt;parse(&apos;foo.flv&apos;); |
| | 312 | print Dumper $data; |
| | 313 | </code></pre><p>C言語でバイナリ解析する場合に比べると、細かい構造体単位でstruct定義を分ける必要がなく、複雑な構造であっても見た目に理解しやすいのがいいところです。制御構文のように使える関数もあるので、「bit 31から28の値が0001なら構造A、0010なら構造Bとして次のN Byteを解析する」というような処理も書きやすいです。</p><p>処理速度は速くないのでリアルタイムで処理するような用途には向きませんが、バイナリエディタで入れ子になったデータ構造のオフセットを確認しながら手動で値を確認するよりわかりやすいでしょう。また仕様書から複雑なデータフォーマットを学ぶ時にも理解の助けとなると思います。</p><p><a href="http://perl-users.jp/articles/advent-calendar/2008/">Back</a></p></body> |
| | 314 | </content:encoded> |
| | 315 | <dcterms:modified>2008-12-24T18:14:47Z</dcterms:modified> |
| | 316 | <guid isPermaLink="false">tag:perl-users.jp,2006:http://perl-users.jp/articles/advent-calendar/2008/25.html</guid> |
| | 317 | </item> |
| | 318 | <item> |
| | 319 | <author>nobody@example.com</author> |
| | 320 | <dc:creator>nobody@example.com</dc:creator> |
| | 321 | <link>http://perl-users.jp/articles/advent-calendar/2008/24.html</link> |
| | 322 | <description>関数をラップするウェブサービス等のAPI とやり取りするモジュールを使って開発している際、デバッグのためAPI からのレスポンスをのぞき見たいときがあると思います(自分はさっきありました)。そんなとき、関数をラップすると便利です。型グロブmy $orig_request = LWP::UserAgent->can('request'); |
| | 323 | *LWP::UserAgent::request = sub { |
| | 324 | use Data::Dumper; |
| | 325 | my $response = $orig_request->(@_); |
| | 326 | print Dumper($response); |
| | 327 | return $response; |
| | 328 | }; |
| | 329 | Hook::LexWrapuse Hook::LexWrap; |
| | 330 | wrap 'LWP::UserAgent::request', |
| | 331 | post => sub { |
| | 332 | use Data::Dumper; |
| | 333 | my $response = $_[-1]; |
| | 334 | print Dumper($response); |
| | 335 | }; |
| | 336 | 各ラッパー関数にはラップ元の関数と同じ引数リストが渡されます。加えて$_[-1]に関数の戻り値が設定されます。$_[-1]を変更することで関数の最終的な戻り値を変更できます。つぎはkoyachiさんお願いします。Back |
| | 337 | </description> |
| | 338 | <dc:date>2008-12-24T04:12:13Z</dc:date> |
| | 339 | <title>関数をラップする</title> |
| | 340 | <pubDate>Wed, 24 Dec 2008 04:12:13 -0000</pubDate> |
| | 341 | <content:encoded><body><h1>関数をラップする</h1><p>ウェブサービス等のAPI とやり取りするモジュールを使って開発している際、デバッグのためAPI からのレスポンスをのぞき見たいときがあると思います(自分はさっきありました)。</p><p>そんなとき、関数をラップすると便利です。</p><h2>型グロブ</h2><pre class="lang-perl"><code>my $orig_request = LWP::UserAgent-&gt;can(&apos;request&apos;); |
| | 342 | *LWP::UserAgent::request = sub { |
| | 343 | use Data::Dumper; |
| | 344 | my $response = $orig_request-&gt;(@_); |
| | 345 | print Dumper($response); |
| | 346 | return $response; |
| | 347 | }; |
| | 348 | </code></pre><h2><a href="http://search.cpan.org/perldoc?Hook::LexWrap">Hook::LexWrap</a></h2><pre class="lang-perl"><code>use Hook::LexWrap; |
| | 349 | wrap &apos;LWP::UserAgent::request&apos;, |
| | 350 | post =&gt; sub { |
| | 351 | use Data::Dumper; |
| | 352 | my $response = $_[-1]; |
| | 353 | print Dumper($response); |
| | 354 | }; |
| | 355 | </code></pre><p>各ラッパー関数にはラップ元の関数と同じ引数リストが渡されます。加えて<code>$_[-1]</code>に関数の戻り値が設定されます。</p><p><code>$_[-1]</code>を変更することで関数の最終的な戻り値を変更できます。</p><p>つぎは<a href="http://tako3.com/http://buffr.org/">koyachi</a>さんお願いします。</p><p><a href="http://perl-users.jp/articles/advent-calendar/2008/">Back</a></p></body> |
| | 356 | </content:encoded> |
| | 357 | <dcterms:modified>2008-12-24T04:12:13Z</dcterms:modified> |
| | 358 | <guid isPermaLink="false">tag:perl-users.jp,2006:http://perl-users.jp/articles/advent-calendar/2008/24.html</guid> |
| | 359 | </item> |