文書化されていないAPIは必要ですか?

高水準プログラミング言語の美しさは何ですか?

プログラマーは「実際にどのように機能するか」について考えるのをやめ、山に無数のコードを大量に与えます。 さらに、彼は必要な知識の平凡な欠如を持っているので、彼はそれについて考えることさえしません(また、彼らは今それを教えています)。



それから、これらすべてが、「デザインパターン」、涙なしで見られない内部フレームワーク、および基本的にプログラミングであるサードパーティのペイザニンが理解できるように作成する必要のある技術文書を含む、他の明暗法などの巧妙な言葉を思いつきます(はいおよび職権上)、最も遠い関係にあります。

言葉は賢明です、高レベルのコードには合うかもしれませんが、...



そして、次の賢い「パターン」を完成させた後、彼はアルゴリズムがどこで減速するかを理解し始めます。 さらに、プログラマーがより永続的であれば、彼はVCLの実装を研究し、時々、彼が知っているAPIの呼び出しに対してブレーキがかかっていることが判明し、バグトラッカーでチケットを冷静に停止してクローズするフレーズを見つけます: XXXは遅くなり、回避策はありません。



状況に合っていませんか?

とてもラッキー...







このようなクローズされたチケットのオプションの1つを示します。これは、一時的にターゲットアプリケーションの速度に大きな影響を与えました。 もちろん、問題は異なっていたので、私は少し遠慮がちですが、原則として問題の本質も反映しています。



指定したプロセスの現在のヒープの最新リストを必要とするアプリケーションを作成するとします(理由は関係ありませんが、ウイルス対策プログラムがあるとしましょう:))。



単純化するために、現在のプロセスからヒープのリストを取得します。このため、空のコンソールアプリケーションを作成し、そこに次のコードを記述します。



procedure QueryProcessHeap1; var hSnapShot: THandle; HeapList: THeapList32; HeapEntry: THeapEntry32; Start: DWORD; begin Start := GetTickCount; Writeln('Heap info:'); Writeln; hSnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPHEAPLIST, 0); try HeapList.dwSize := SizeOf(THeapList32); if Heap32ListFirst(hSnapShot, HeapList) then repeat HeapEntry.dwSize := SizeOf(THeapEntry32); if Heap32First(HeapEntry, GetCurrentProcessId, HeapList.th32HeapID) then repeat Writeln(Format('Heap addr: 0x%p, size: %d', [Pointer(HeapEntry.dwAddress), HeapEntry.dwBlockSize])); until not Heap32Next(HeapEntry); until not Heap32ListNext(hSnapShot, HeapList); finally CloseHandle(hSnapShot); end; Writeln; Writeln('DONE. Time elapsed: ', GetTickCount - Start); end;
      
      







コードは単純から不可能です-標準のTlHelp32関数は簡単に使用できます。

しかし、ニュアンス、それを実行し、その作業の時間を見てみてください。



約150ミリ秒。 最適化されていないコードを使用してコンピューターのパワーを平準化しようとしているという事実についてのジョークは確かにばかげていますが、...

プロセスからのみこのデータを取得しようとしましたが、ユーザーのマシン上にはどれくらいあるでしょうか? はい、少なくとも50ドル、そして私たちは何に行きますか?



プロセスヒープのみを要求する場合は、ほぼ5秒の遅延があります(スレッドプロセスの一覧表示などではありません)。



まあ、本当に、しかし、私たちは何ができますか?



私たちのせいではありません-これがHeap32xxxが機能する方法であり、実際、これらのブレーキは呼び出しの遅延によって引き起こされます(これは本当にそうです)。 そして今、私たちはサービスと共にサーバーに座って、プロセッサリソースを食いつぶし、「FSEはなくなった-ボス」と肩をすくめています。



何もありません-私たちが作った障壁に立ち止まったことを責めなければなりません。



プログラマーは常に手元にデバッガーを持っています。これにより、少なくともCPU-Viewを使用して呼び出しスタックを拡張することにより、正確に何が起こっているかを分析できます。



私はソフトウェア保護に興味のある人向けの記事を書いていることを考慮して、それを使用する方法を教えません(あなたはこれを自分で知っている必要があります)。



数十のトレース手順の後、以下が表示されます。



画像



トラフィックをもう少し増やして、次の場所に移動します。



画像



その後、同じ手順を実行しますが、Heap32Next関数のみを使用します。



そこに何があると思いますか?

悲しいかな-同じRtlCreateQueryDebugBuffer + RtlQueryProcessDebugInformationおよびRtlDestroyQueryDebugBufferの最後。



しかし、これはすでに問題です。何が起きているのでしょうか? tlhelp32.dllライブラリからAPI関数を呼び出すと、 これら3つのAPI関数はすべて実際に呼び出されます。最初の関数はプロセスに関する情報を収集し、2番目の関数はそこから1つのレコードのみを選択してからすべてを閉じます。 高価すぎますか?



ここで覚えておきましょう-プロセスヒープの一覧表示にどれくらいの時間がかかりましたか? 150ミリ秒? ええ、はい、なぜですか、Heap32Nextの呼び出しごとにすべてのプロセス情報が再収集されたとき。



私たちは戦います-コードを書きます(すぐに警告します、コードはひざまずく、アドレスの出力と少し混同しますが、デモには役立ちます)。



 procedure QueryProcessHeap2; var I, A: Integer; pDbgBuffer: PRtlDebugInformation; pHeapInformation: PRtlHeapInformation; pHeapEntry: PRtrHeapEntry; dwHeapStartAddr, dwAddr, dwLastSize: DWORD; hit_seg_count: Integer; Start: DWORD; begin Start := GetTickCount; Writeln('Heap info:'); Writeln; pDbgBuffer := RtlCreateQueryDebugBuffer(0, False); if pDbgBuffer <> nil then try //       if RtlQueryProcessDebugInformation(GetCurrentProcessId, RTL_QUERY_PROCESS_HEAP_SUMMARY or RTL_QUERY_PROCESS_HEAP_ENTRIES, pDbgBuffer) = 0 then begin //       pHeapInformation := @pDbgBuffer^.Heaps^.Heaps[0]; //    ... for I := 0 to pDbgBuffer^.Heaps^.NumberOfHeaps - 1 do begin //     pHeapEntry := pHeapInformation^.Entries; dwAddr := DWORD(pHeapEntry^.u.s2.FirstBlock) + pHeapInformation^.EntryOverhead; dwLastSize := 0; for A := 0 to pHeapInformation^.NumberOfEntries - 1 do begin dwHeapStartAddr := dwAddr; hit_seg_count := 0; while (pHeapEntry^.Flags and RTL_HEAP_SEGMENT) = RTL_HEAP_SEGMENT do begin //     RTL_HEAP_SEGMENT, //     dwAddr := DWORD(pHeapEntry^.u.s2.FirstBlock) + pHeapInformation^.EntryOverhead; Inc(pHeapEntry); Inc(hit_seg_count); //      if A + hit_seg_count >= Integer(pHeapInformation^.NumberOfEntries - 1) then Continue; end; //       ,     , //    +    if hit_seg_count = 0 then Inc(dwAddr, dwLastSize); Writeln(Format('Heap addr: 0x%p, size: %d', [Pointer(dwHeapStartAddr), dwAddr - dwHeapStartAddr])); //     dwLastSize := pHeapEntry^.Size; //     Inc(pHeapEntry); end; //     Inc(pHeapInformation); end; end; finally RtlDestroyQueryDebugBuffer(pDbgBuffer); end; Writeln; Writeln('DONE. Time elapsed: ', GetTickCount - Start); end;
      
      







そして、ここには何がありますか?

しかし、加速は4倍以上であり、実際にはほんのわずかなことでした。



文書化されていませんか?

彼との道化師、微妙さを掘り下げることをheしないでください:)

「できるとき」と「必要なとき」の間の距離を忘れないでください:)



文書化されていない呼び出しを使用することの欠点は何ですか?

まず、これらの関数を呼び出すためのパラメーターと構造が次のパッチで変更されないことを保証することはできません。実際、ライブラリーのエクスポートリストに自分自身が残ることを保証することはできません。 正直なところ、AllocateAndGetTcpExTableFromStackという1つの関数を除いて、このような動作に遭遇したことはありません。 しかし、それで、一般に、すべてがどういうわけか明確ではないことが判明し、最初はWindows XPに登場しました。 Vistaがリリースされるまで、ドキュメント化されていないと見なされていましたが、Vistaのリリースではドキュメント化されましたが、すぐにIphlpapi.dllライブラリから除外され、「この機能はWindows Vistaで使用できなくなりました」と書きました。



2番目のマイナス:文書化された機能の代替として、より生産的な類似物を見つけることが常に可能とは限らず、実際、ほとんどの場合、それは必要ありません。 最適化のための最適化は、ソフトウェア開発にとって十分に悪いアプローチです...



ソースコードはここで見つけることができます: rouse.drkb.ru/blog/heap.zip



Alexander(Rouse_)ベーグル

2012年12月



All Articles