質問

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

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

このプログラムはもともと別の会社によって書かれ、私のクライアントに譲渡され、その後別のコンサルタントによって 9 年前に修正されました。元の会社もコンサルタントもリソースとして利用できません。

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

元の質問:破損することなく変更を加えるには、プログラムを十分に理解する必要があります。この種の混乱を処理するための技術を開発した人はいますか?

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

役に立ちましたか?

解決

まず、最初にそのコードを開発した人、または少なくとも私より前にそのコードを保守していた人たちに連絡を取って、できればコード全体の基本を理解するのに十分な情報を入手して、有用なコメントを追加し始めることができるようにします。それ。

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

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

あなたの雇用主がそのような技術的な背景を持っていない場合は、別のプログラマ/同僚を連れてきて自分の手順を説明してもらうように依頼してください。そうすることで、実際にあなたがそれについて真剣で正直であることを彼に示すことができます。なぜなら、それは現実的な問題だからです。あなたの視点から (この「プロジェクト」について知っている同僚がいることを確認してください)。

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

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

これ自体も、彼らがあなたの状況を評価し、理解するのに役立ちます。あなたが今やるべきことをするのは決して簡単なことではなく、彼らはそれについて知っておくべきです - 彼らの期待を正すためだけに(例:期限とタスクの複雑さについて)。

また、個人的には、十分に理解している部分に対して単体テストを追加し始めて、コードのリファクタリングや書き直しをゆっくり始められるようにします。

言い換えれば、優れたドキュメントとソース コードのコメントは別のことですが、包括的なテスト スイートを持つことは別の重要なことであり、主要な機能をテストする確立された方法がなければ、馴染みのないコード ベースを変更することは現実的には期待できません。

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

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

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

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

さらに、それが実行可能な選択肢である場合は、C のサブセットを使用するか、少なくともコードの標準化を助けるためにアセンブリ マクロをかなり過剰に使用することにより、より高級な言語を使用してコードの一部を段階的に書き直すことを検討します。ベースだけでなく、潜在的なバグの場所を特定するのにも役立ちます。

C での増分書き換えが実現可能なオプションである場合、考えられる開始方法の 1 つは、すべての明白な関数を C 関数に変換し、その本体が最初にアセンブリ ファイルからコピー/ペーストされ、最終的に C になるというものです。多くのインラインアセンブリを使用する関数。

個人的には、次のコードを実行してみることもできます。 シミュレータ/エミュレータ コードを簡単にステップ実行して、できれば最も重要な構成要素の理解を開始するには (レジスタとスタックの使用法を調べながら)、実際にこれを主に実行する必要がある場合は、デバッガが組み込まれた優れた 8051 シミュレータを利用できるようにする必要があります。自分の。

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

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

私があなたの状況にいたら、このソース コードの操作を簡素化するためにツールを変更する作業をアウトソーシングすることさえ検討します。多くのsourceforgeプロジェクトは寄付を受け付けているので、雇用主にそのような変更を後援するよう説得できるかもしれません。

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

すでに独自の製品を使用している場合は、このソフトウェアの製造元に相談して要件を詳細に説明し、この製品をそのように改善する意思があるかどうか、または少なくとも次のことを可能にするインターフェイスを公開できるかどうかを尋ねることもできるかもしれません。顧客はそのようなカスタマイズを行うことができます (何らかの形式の内部 API、または場合によっては単純な接着スクリプト)。

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

ソフトウェアが特定の I/O ハードウェアおよび周辺機器を必要とする場合は、エミュレータでソフトウェアを実行するための対応するハードウェア シミュレーション ループの作成を検討することもできます。

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

オープンソースの 8051 エミュレータから使用可能なコールグラフを取得するには、(せいぜい) 週末程度の時間がかかるはずです。これは主に、CALL オペコードを探してそのアドレス (位置とターゲット) を記録し、すべてがファイルにダンプされるためです。後で検査できるようにファイルに保存します。

エミュレータの内部にアクセスできることは、コードをさらに検査するための優れた方法でもあります。たとえば、スタンドアロンの関数/プロシージャに組み込まれている可能性のあるオペコードの繰り返しパターン (たとえば 20 ~ 50+) を見つけるために、これは実際には可能です。コードベースのサイズと複雑さをさらに軽減するのに役立ちます。

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

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

また、実際には、長期的にはより良いドキュメントを作成するための基盤として機能する有用なデータとドキュメントが得られます。

他のヒント

私はこの種の問題に特効薬はありません怖いです。私が唯一の解決策はどこか静かで行くことと、(メモ帳に登録すると、メモリ位置の内容を書きながら)あなたの心の中でプログラムを1行ずつ実行しているシミュレートするために、そして、ASMファイルをプリントアウトすることです見つけます。しばらくして、あなたは、これは限り、あなたが期待するようになりません見つけます。 これを行うに多くの時間を費やし、コーヒーのガロンを飲むように調製すること。しばらくして、あなたはそれが何をしているかを理解しているだろうし、変更を検討することができます。

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

幸運

私はこれがクレイジーに聞こえる知っている....が、私は失業しています(私は地獄に行くことにmarjorityパートナーを伝えるために間違った時間を選んだ)といくつかの自由な時間を持っています。私はそれを見てみることをいとわないだろう。私はアップル] [、元のPCのためのアセンブリを記述するために使用しました。私は数時間のためにシミュレータ上で、あなたのコードで遊ぶことができれば、私は(私の計画外の休暇をruningてなくて)あなたのためにそれを文書化する機会を持っている場合、私はあなたのアイデアを与えることができます。私は8051について何も知らないので、これは私のような誰かのために可能ではないかもしれませんが、シミュレータは有望に見えました。私はこれを行うにはお金を望んでいないでしょう。ちょうど8051組込み開発への露出を得るために、その十分。私はこれがクレイジーに聞こえるだろうあなたに言っています。

真剣に別のjob-を探します!私はそれがユニットテストなしのコードなどのレガシーコードを参照していると思いますけれども、「レガシーコードと効果的に働く」本はヘルプ - かもしれないと失敗ます。

私も何度かこのようなことをしたことがあります。いくつかの推奨事項:

  • 概略図を確認することから始めます。これは、必要な変更の影響をどのポートとピンするかを理解するのに役立つはずです。
  • GREPを使用して、すべての呼び出し、ブランチ、ジャンプ、リターンを見つけます。これは、フローを理解し、コードのチャンクを識別するのに役立ちます。
  • リセットベクトルと割り込みテーブルを見て、メインラインを識別します。
  • GREPを使用して、すべてのコードラベルとデータ参照のクロスリファレンスを作成します(アセンブラーツールがこれを実行できない場合)。

ホフスタッターの法則に留意してください。ホフスタッターの法則を考慮した場合でも、常に予想より時間がかかります.

幸運を。

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

  • パワーを節約するために、それがどのように目覚められたかを節約するために、それは電源ダウンモード(PCON = 2)に入れられましたか?(リセットまたはハードウェア割り込み時)

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

  • スリープモード(Pcon=1)に入っていませんか?

現場ではさまざまなバージョンのハードウェアが存在しますか?

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

シミュレータで時間を無駄にしないでください。シミュレータは非常に扱いが難しく、ハードウェアについて多くの仮定を立てる必要があります。取得してください インサーキットエミュレータ(ICE) そしてハードウェア上で実行します。

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

このコードが混乱しているのには理由があるかもしれません

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

XDATA スペース、IDATA スペース、およびコードスペース:

空きコードスペースや Xdata または Idata がない場合はどうすればよいですか?

オリジナルの作成者が、利用可能なメモリ領域に収まるように最適化した可能性があります。

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

あなたはリファクタリングとテストのための特別な予算を必要としない - 彼らはあなたのお金を節約し、あなたがより速く動作してみましょう - それを取得します。それは「破損なし」せずにそれを行うには最も安い方法ですので、それはあなたがレガシーに変更を加えるために使用すべき技術だ、コードを継承します。

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

これは、私はあなたが動作するようにあなたのソフトスキルを入れてお勧めします、そして、あなたの再書き込みの背後にある理由、そしてそのように関わる時間/コスト削減をごPM /マネージャー/ CXOを提示するつもりです数回の一つであります事業

の部分にそれをカットします。

8052 ソフトウェアでも同様の問題が発生しました。そこで同社は、コード ROM がフル (64K バイト)、約 1.5 メガのアセンブリ スパゲッティ モジュールと 2 つの 3000 行の PL/M モジュールを加えて、この巨大なコーディングを構成した猛獣を引き継ぎました。このソフトウェアの元の開発者はとっくの昔に亡くなっており (これは、誰もいなかったという意味ではありませんが、実際にこのソフトウェア全体を理解できる人が誰もいなかったということです)、これらをコンパイルしていたコンパイラは、MDS-70 エミュレータ上で実行されていた 80 年代半ばのもので、いくつかの重要な開発者はモジュールはこれらのコンパイラの限界に達していました。グローバル シンボルをもう 1 つ追加すると、リンカーがクラッシュします。ASM ファイルにシンボルをもう 1 つ追加すると、コンパイラがクラッシュします。

では、どうやってこれを切り出すことができるでしょうか?

まず工具が必要になります。たとえば、Notepad++ は、複数のファイルを一度に横断検索できるため、非常に優れており、どのモジュールがグローバル シンボルを参照しているかを見つけるのに最適です。これはおそらく最も重要な要素です。

可能であれば、ソフトウェアに記載されている資料を入手してください。これらの獣に関して解決すべき最も差し迫った問題は、それらがどのように大まかに構成され、その構造が何であるかを理解することです。これは通常、適切にコメントが付けられている場合でも、ソフトウェア自体には含まれていません。

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

これを行うには、ファイル間検索用のツールを使用し、どのシンボルがどのファイルで定義されているか、およびどのファイルがそのシンボルを参照しているかを収集する大きなリスト (OpenOffice Calc など) を作成します。

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

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

最後まで、自分自身のためにすでにどこかで作業している場合を除き、概念的なモジュールを含む階層的なコール グラフが得られます。これから、ソフトウェアの意図的なアーキテクチャを推定し、さらに作業を進めることができます。

次の目標は、 建築. 。以前に作成したマップを使用して、ソフトウェアに沿って移動し、そのスレッド (割り込みおよびメイン プログラム タスク)、および各モジュール/ソース ファイルの大まかな目的を把握する必要があります。これをどのように実行できるか、またここで何が得られるかは、アプリケーション ドメインによって異なります。

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

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

私はIanWの答えは(ちょうどそれをプリントアウトし、トレースを保つ)おそらく最高だと思います。

:それは言った、私は少し壁オフのアイデアを持っています (あなたが8051のための1つを見つけることができるかどうか)

Cコードを再構築することができ似非者によるコード(おそらくバイナリ)を実行してみてください。多分それはあなたがすることはできませんいくつかのルーチン(簡単に)を識別します。

たぶんそれがお手伝いします。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top