17

私は 8051 アセンブリ言語で書かれた 10K 行のプログラムを継承しましたが、これにはいくつかの変更が必要です。残念ながら、これはスパゲッティ コードの最高の伝統で書かれています。単一のファイルとして記述されたプログラムは、CALL ステートメントと LJMP ステートメントの迷路 (合計約 1200) であり、サブルーチンがサブルーチンとして識別できる場合でも、複数の入口点および/または出口点を持つサブルーチンがあります。すべての変数はグローバルです。コメントがあります。いくつかは正しいです。既存のテストはなく、リファクタリングの予算もありません。

アプリケーションの背景: このコードは、現在国際的に展開されている自動販売アプリケーションの通信ハブを制御します。2 つのシリアル ストリームを (別の通信プロセッサを使用して) 同時に処理し、それぞれ異なるベンダーの最大 4 つの異なる物理デバイスと通信できます。最近、デバイスの 1 つの製造元が変更を加えました (「ええ、変更を加えましたが、ソフトウェアはまったく同じです!」)。これにより、一部のシステム構成が機能しなくなり、それを元に戻すことには関心がありません (それが何であれ)。それらは変更されませんでした)。

このプログラムはもともと別の会社によって作成され、私のクライアントに転送され、9 年前に別のコンサルタントによって変更されました。元の会社もコンサルタントもリソースとして利用できません。

シリアル バスの 1 つのトラフィックの分析に基づいて、ハックを思いつきました。これは機能しているように見えますが、見苦しく、根本的な原因に対処していません。プログラムをよりよく理解していれば、実際の問題に対処できると思います。月末の出荷日をサポートするために、コードが凍結されるまであと約 1 週間あります。

元の質問: 壊れることなく変更できるように、プログラムを十分に理解する必要があります。この種の混乱を処理するためのテクニックを開発した人はいますか?

ここに素晴らしい提案がいくつかありますが、時間に制限があります。しかし、将来、より複雑な行動方針を追求する別の機会があるかもしれません.

4

11 に答える 11

17

まず、最初にコードを開発した人、または少なくとも私より前にコードを保守した人に連絡を取り、コード全般の基本的な理解を得るのに十分な情報を得て、有益なコメントを追加できるようにします。それ。

コードの最も重要な API (署名、戻り値、目的を含む) について誰かに説明してもらうこともできます。グローバル状態が関数によって変更される場合、これも明示的にする必要があります。同様に、関数とプロシージャ、および入力/出力レジスタを区別することから始めます。

この情報が必要であることを雇用主に明確に伝えてください。雇用主があなたを信じない場合は、実際にこの規範の前に座ってもらい、何をすべきか、どのようにすべきかを説明する必要があります。それ(リバースエンジニアリング)。その場合、コンピューティングとプログラミングのバックグラウンドを持つ雇用主を持つことは実際に役立ちます!

あなたの雇用主がそのような技術的背景を持っていない場合は、別のプログラマー/同僚を連れてきてあなたの手順を説明するように依頼してください。あなたの観点から(この「プロジェクト」について知っている同僚がいることを確認してください)。

利用可能で実行可能であれば、このコードの文書化を支援するために、以前の開発者/メンテナ (つまり、彼らがあなたの会社で働いていない場合) と契約する (または少なくとも連絡する) ことは、事前の準備になることも非常に明確にします。 -短期間でコードを現実的に改善し、将来的により簡単に保守できるようにする必要があります。

この全体的な状況は、以前のソフトウェア開発プロセスの欠点によるものであり、これらの手順がコード ベースの改善に役立つことを強調してください。そのため、現在の形式のコード ベースは増大する問題であり、この問題を処理するために現在行われていることは、将来への投資です。

これ自体も、彼らがあなたの状況を評価し、理解するのを助けるために重要です: あなたが今やるべきことをすることは簡単なことではありません.彼らはそれについて知っておくべきです.タスク)。

また、個人的には、十分に理解している部分の単体テストを追加して、コードのリファクタリング/リライトをゆっくりと開始できるようにします。

言い換えれば、優れたドキュメントとソース コード コメントも重要ですが、包括的なテスト スイートを用意することも重要です。

コードが 10K であることを考えると、できればグローバル変数の代わりにアクセス ラッパーと直感的なファイル名を使用して、コンポーネントをより識別しやすくするために、サブルーチンを個別のファイルに分割することも検討します。

さらに、複雑さを軽減することでソース コードの可読性をさらに向上させるための手順を検討します。サブルーチンに複数のエントリ ポイント (および場合によっては異なるパラメーター シグネチャも?) を持たせることは、コードを不必要に難読化する確実な方法のように見えます。

同様に、読みやすさを向上させるために、巨大なサブルーチンを小さなサブルーチンにリファクタリングすることもできます。

したがって、私が最初に検討することの 1 つは、コード ベースを理解するのが非常に複雑になる原因を特定し、それらの部分を作り直すことです。たとえば、複数のエントリ ポイントを持つ巨大なサブ ルーチンを個別の代わりに相互に呼び出すサブルーチン。パフォーマンス上の理由または呼び出しのオーバーヘッドが原因でこれを実行できない場合は、代わりにマクロを使用してください。

さらに、それが実行可能なオプションである場合は、C のサブセットを使用するか、少なくともコードの標準化を支援するためにアセンブリ マクロをかなり過度に使用することにより、より高レベルの言語を使用してコードの一部を段階的に書き直すことを検討します。ベースだけでなく、潜在的なバグのローカライズにも役立ちます。

C でのインクリメンタルな書き換えが実行可能なオプションである場合、開始するための 1 つの可能な方法は、すべての明白な関数を、最初にアセンブリ ファイルからコピー/貼り付けされた本体を持つ C 関数に変換することです。インライン アセンブリが多い関数。

個人的には、シミュレーター/エミュレーターでコードを実行してコードを簡単にステップ実行し、(レジスターとスタックの使用法を調べながら) 最も重要なビルディング ブロックの理解を開始できることを願っています。組み込みデバッガーを備えた優れた 8051 シミュレーターはこれは、主に自分で行う必要がある場合に利用できます。

これは、初期化シーケンスとメイン ループ構造、およびコールグラフを考え出すのにも役立ちます。

おそらく、完全なコールグラフを自動的に提供するように簡単に変更できる優れたオープンソースの 80851 シミュレーターを見つけることもできます。簡単な検索を行うだけでgsim51が見つかりましたが、明らかに他にもいくつかのオプションがあり、さまざまな独自のものもあります。

もし私があなたの立場なら、ツールを変更してこのソースコードの作業を簡素化する作業をアウトソーシングすることも検討します。つまり、多くの sourceforge プロジェクトは寄付を受け入れており、雇用主にそのような変更を後援するように依頼することができます。

経済的でない場合は、それに対応するパッチを提供することでしょうか?

すでにプロプライエタリな製品を使用している場合は、このソフトウェアの製造元と話をして要件を詳しく説明し、この製品をそのように改善する意思があるかどうか、または少なくともインターフェースを公開して許可することができるかどうかを尋ねることもできます。顧客はそのようなカスタマイズを行うことができます (何らかの形式の内部 API または単純なグルー スクリプトでさえ)。

彼らが反応しない場合は、あなたの雇用主がしばらくの間別の製品を使用することを考えていたこと、そしてその特定の製品を使用することを主張したのはあなただけだったことを示してください... ;-)

ソフトウェアが特定の I/O ハードウェアおよびペリフェラルを想定している場合は、対応するハードウェア シミュレーション ループを記述して、エミュレータでソフトウェアを実行することを検討することもできます。

最終的には、コーヒーを何ガロン飲んでも、手動でコードをステップ実行して自分でエミュレーターをプレイするよりも、このようなスパゲッティ コード モンスターを理解するのに役立つ他のソフトウェアをカスタマイズするプロセスを個人的にはるかに楽しむことができるという事実を私は知っています。得る。

オープンソースの 8051 エミュレーターから使用可能なコールグラフを取得するのに、(せいぜい) 週末ほどかかることはありません。これは、ほとんどの場合、CALL オペコードを探してそのアドレス (位置とターゲット) を記録することを意味するためです。後で検査するためにファイルします。

エミュレーターの内部にアクセスすることは、実際には、コードをさらに検査するための優れた方法でもあります。コード ベースのサイズと複雑さをさらに軽減するのに役立ちます。

次のステップは、おそらくスタックとレジスタの使用状況を調べることです。また、使用される関数パラメーターのタイプ/サイズ、およびそれらの値の範囲を決定して、対応する単体テストを考えられるようにします。

dot/graphviz のようなツールを使用して、初期化シーケンスとメイン ループ自体の構造を視覚化することは、これらすべてを手動で行うことに比べて純粋に楽しいものです。

また、実際には、長期的にはより優れたドキュメントの基盤となる有用なデータとドキュメントが得られます。

于 2009-06-11T21:23:00.877 に答える
7

残念ながら、この種の問題に対する特効薬はありません。唯一の解決策は、ASM ファイルを印刷してから静かな場所に移動し、頭の中でプログラムを 1 行ずつ実行するようにシミュレートすることです (レジスタの内容とメモリ位置をメモ帳に書きながら)。しばらくすると、これは思ったほど長くはかからないことがわかります。これに何時間も費やし、ガロンのコーヒーを飲む準備をしてください。しばらくすると、それが何をしているかを理解し、変更を検討できるようになります。

8051 に未使用の IO ポートはありますか? そうで、特定のルーチンが呼び出されているときにうまくいかない場合は、これらの予備ポートをハイまたはローに送信するコードを追加します。次に、プログラムの実行中に、これらのポートをオシロスコープで監視します。

幸運を

于 2009-06-11T21:04:13.573 に答える
6

おかしなことに聞こえるかもしれませんが、私は失業しており(結婚相手に地獄に行くように言うのに間違った時間を選びました)、自由な時間があります。喜んで見てみたいと思います。Apple ][ と元の PC のアセンブリを書いていました。数時間シミュレーターであなたのコードをいじってみることができれば、あなたのためにそれを文書化する機会があれば (予定外の休暇を実行することなく) アイデアを提供できます。私は 8051 について何も知らないので、これは私のような人には不可能かもしれませんが、シミュレーターは有望に見えました。私はこれを行うためにお金が欲しくありません。8051 組込み開発に触れるだけで十分です。これはクレイジーに聞こえると言った。

于 2009-06-11T22:55:58.487 に答える
4

別の仕事を見つけてください-真剣に!「レガシーコードで効果的に機能する」という本が役に立たないかもしれませんが、レガシーコードを単体テストのないコードと呼んでいると思います。

于 2009-06-11T21:01:06.467 に答える
4

私はこの種のことを数回行いました。いくつかの推奨事項:

  • 回路図を確認することから始めます。これは、目的の変更がどのポートとピンに影響を与えるかを理解するのに役立ちます。
  • grep を使用して、すべての呼び出し、分岐、ジャンプ、およびリターンを検索します。これは、フローを理解し、コードのチャンクを識別するのに役立ちます。
  • リセット ベクタと割り込みテーブルを見て、メイン ラインを特定します。
  • grep を使用して、すべてのコード ラベルとデータ参照の相互参照を作成します (アセンブラー ツールでこれを実行できない場合)。

ホフスタッターの法則に注意してください: ホフスタッターの法則 を考慮しても、常に予想よりも時間がかかります

幸運を。

于 2009-06-12T14:30:51.607 に答える
3

このコードが実行されているハードウェア プラットフォームをどの程度理解していますか?

  • 電力を節約するためにパワー ダウン モード (Pcon=2) に設定されていますか。(リセットまたはハードウェア割り込み)

  • シリアル通信を行う前に、電源投入後に発振器が安定するまで待つ必要がありますか?

  • スリープ状態になっていませんか (Pcon=1)

現場にハードウェアの異なるバージョンがありますか?

テストするハードウェアのバリエーションがすべて揃っていることを確認してください。

シミュレーターで時間を無駄にしないでください。使用するのは非常に難しく、ハードウェアについて多くの仮定を立てる必要があります。In Circuit Emulator (ICE)を入手して、ハードウェア上で実行してください。

ソフトウェアはアセンブラーで書かれており、その理由を調べる必要があります。すなわち - メモリの制約 - 速度の制約

このコードがめちゃくちゃな理由があるかもしれません

次のリンク ファイルを参照してください。

XDATA SPACE、IDATA SPACE、および CODE SPACE:

空きコード スペースまたは Xdata または Idata がない場合は?

元の作成者は、利用可能なメモリ空間に収まるように最適化した可能性があります。

その場合は、元の開発者と話をして、彼が何をしたかを調べる必要があります

于 2009-06-11T21:52:25.627 に答える
1

リファクタリングとテストに特別な予算は必要ありません。費用を節約し、より迅速に作業できるようになります。これは、継承されたレガシー コードに変更を追加するために使用する必要がある手法です。これは、「破損せずに」行うための最も安価な方法だからです。

ほとんどの場合、より多くの時間を費やすことと引き換えに、より多くの品質を得るというトレードオフがあると思いますが、慣れていないレガシー コードでは、テストを作成する方が速いと思います。コードを実行する前にコードを実行する必要があります。あなたはそれを出荷しますよね?

于 2009-06-11T21:01:06.123 に答える
1

これは、ソフト スキルを活用し、PM/マネージャー/CXO に書き直しの理由と、そのような取り組みに伴う時間/コストの削減を提示することをお勧めする数少ない機会の 1 つです。

于 2009-06-11T21:01:59.010 に答える
1

それを細かく切る。

于 2009-06-11T23:03:51.727 に答える
1

8052 ソフトウェアで非常によく似た問題が発生しました。そのため、会社はそのような野獣、コード ROM フル (64K バイト)、約 1.5 メガのアセンブリ スパゲッティ モジュール、およびこのコーディングの怪物を構成する 2 つの 3000 行 PL/M モジュールを継承しました。ソフトウェアの最初の開発者は長い間死んでいました (これは誰もいなかったという意味ではありませんが、全体としてそれを理解する人は誰もいませんでした)。モジュールは、これらのコンパイラの限界にありました。グローバル シンボルをもう 1 つ追加すると、リンカーがクラッシュします。ASM ファイルにシンボルをもう 1 つ追加すると、コンパイラがクラッシュします。

では、どうすればこれを切り分け始めることができるでしょうか?

まず、ツールが必要になります。たとえば、Notepad++ は、一度に複数のファイルをクロス検索するために使用できるため、非常に優れた機能であり、グローバル シンボルを参照するモジュールを見つけるのに理想的です。これはおそらく最も重要な要素です。

可能であれば、ソフトウェアに記載されている書類を入手してください。これらの獣について解決すべき最も差し迫った問題は、それらがどのように大まかに構成されているか、どのようなアーキテクチャであるかを理解することです。これは通常、別の方法で適切にコメントされていても、ソフトウェア自体には含まれていません。

アーキテクチャを自分で取得するには、まずコール グラフを作成してみてください。通常、グローバル変数よりもファイル間の呼び出しとジャンプが少ないため、データ フロー グラフよりも簡単に実行できます。このコール グラフでは、ソース ファイルがモジュールであると仮定して、グローバル シンボルのみを考慮します (これは必ずしも正しいとは限りませんが、通常は正しいはずです)。

これを行うには、クロスファイル検索用のツールを使用し、どのシンボルがどのファイルで定義されているか、どのファイルがこのシンボルを呼び出しているかを収集する大きなリスト (たとえば OpenOffice Calc で) を作成します。

次に、プロッターからいくつかの大きな (!) シートを盗み、スケッチを開始します。いくつかのグラフ ソフトウェアに非常に習熟している場合は、それを使用することもできますが、そうでない場合は、使用を控える可能性が高くなります。したがって、どのファイルが他のどのファイルへの呼び出しを持っているかを示す呼び出しグラフをスケッチします (シンボル自体を表示しないと、50 個程度のファイルでは管理できません)。

おそらく、この結果はスパゲッティになります。目標は、これをまっすぐにして、ループのないルート (プログラムのエントリ ポイントを含むファイル) を持つ階層ツリーを取得することです。このプロセス中に数枚のシートを繰り返し食べて、獣をまっすぐにすることができます. また、特定のファイルが非常に絡み合っているため、ループなしでは表現できない場合もあります。この場合、単一の「モジュール」が何らかの理由で 2 つのファイルに分離されたか、より概念的なモジュールが絡み合っている可能性が最も高いです。呼び出しリストに戻り、シンボルをグループ化して、問題のあるファイルをより小さな独立した単位に切り分けます (想定される切り取りが可能であることを確認するには、ここでローカル ジャンプがないかファイル自体も確認する必要があります)。

最後まで、自分の利益のために別の場所で既に作業を行っていない限り、概念的なモジュールを含む階層的な呼び出しグラフが得られます。このことから、ソフトウェアの意図的なアーキテクチャを導き出し、さらに作業を進めることができます。

次の目標はアーキテクチャです。以前に作成したマップによって、ソフトウェアに沿ってナビゲートし、そのスレッド (割り込みおよびメイン プログラム タスク) を把握し、各モジュール/ソース ファイルの大まかな目的を理解する必要があります。これを行う方法とここで得られるものは、アプリケーション ドメインによって異なります。

これらの 2 つが完了すると、「残り」はかなり単純になります。これらによって、基本的に各部分が何をすべきかを知る必要があります。したがって、ソース ファイルの作業を開始するときに何を処理する可能性があるかがわかります。ただし、ソースに「怪しい」ものがある場合は、プログラムが無関係なことをしているように見える場合はいつでも、アーキテクチャに戻ってグラフを呼び出し、必要に応じて修正することが重要です。

残りの部分には、他の人が言及した方法がうまく適用されます。本当に恐ろしいケースで何ができるかについての洞察を与えるために、これらの概要を説明しました. 当時、処理するコードが 10,000 行あればよかったのに...

于 2013-07-08T12:40:15.657 に答える
0

IanWの答え(印刷してトレースを続けるだけ)がおそらく最高だと思います。そうは言っても、私は少し壁から離れたアイデアを持っています:

Cコードを再構築できる逆アセンブラを介してコード(おそらくバイナリ)を実行してみてください(8051用のものが見つかった場合)。おそらく、(簡単に) できないいくつかのルーチンを特定するでしょう。

多分それは助けになるでしょう。

于 2009-06-11T21:58:21.027 に答える