Skip to content

Commit 0205cd6

Browse files
committed
複数の逆アセンブラ混在使用可能性の解消
逆アセンブラは初回の選定でキャッシュを使わず実行し、成功したツールを最後まで固定する。以後は同一ツール由来のキャッシュのみ使用する。
1 parent 694d1b4 commit 0205cd6

3 files changed

Lines changed: 98 additions & 27 deletions

File tree

README.en.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This repository hosts a .NET console application that compares two folders, clas
1111
- IL disassembler (the app automatically probes candidates in this order)
1212
- Preferred: `dotnet-ildasm` or `dotnet ildasm`
1313
- Fallback: `ilspycmd`
14+
- The app **locks to a single disassembler per run**. The first disassembly is executed without using cache, and the tool that succeeds is fixed for the rest of the run (only that tool's cache is used afterward). No mixing occurs.
1415

1516
Installation example:
1617

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88

99
- .NET SDK 8.x
1010
- macOS/Windows/Linux/Unix系(例: FreeBSD)で動作
11-
- IL 逆アセンブラ(自動で候補順に試行します)
11+
- IL 逆アセンブラ(自動で候補順に試行します)
1212
- 優先: `dotnet-ildasm` または `dotnet ildasm`
1313
- 代替: `ilspycmd`
14+
- 逆アセンブラは**実行中に1つに固定**します。最初の逆アセンブルはキャッシュを使わず実行し、成功したツールをその実行の間ずっと使い続けます(キャッシュもそのツールのものだけを使用)。混在しません。
1415

1516
インストール例:
1617
```bash

Services/DotNetDisassembleService.cs

Lines changed: 95 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,16 @@ private enum DisassemblerKind
165165
/// ツール毎の連続失敗回数と最終失敗時刻(協定世界時刻)。閾値超過で一定時間ブラックリスト化するために利用。
166166
/// </summary>
167167
private static readonly ConcurrentDictionary<string, (int FailCount, DateTime LastFailUtc)> _disassembleFailCountAndTime = new();
168+
169+
/// <summary>
170+
/// 実行中に固定された逆アセンブラコマンド(混在防止)。
171+
/// </summary>
172+
private string _fixedDisassembleCommand;
173+
174+
/// <summary>
175+
/// 逆アセンブラの初回選択を直列化するためのロック。
176+
/// </summary>
177+
private readonly SemaphoreSlim _disassemblerSelectionLock = new(1, 1);
168178
#endregion
169179

170180
#region private writable member variables
@@ -215,44 +225,99 @@ public DotNetDisassembleService(ConfigSettings config, ILCache ilCache)
215225
/// <returns>逆アセンブル済み IL テキストと、人間が読めるコマンド表示(バージョン付き)をタプルで返します。</returns>
216226
public async Task<(string ilText, string commandString)> DisassembleAsync(string dotNetAssemblyfileAbsolutePath)
217227
{
218-
Exception lastError = null;
228+
var fixedCommand = Volatile.Read(ref _fixedDisassembleCommand);
229+
if (!string.IsNullOrWhiteSpace(fixedCommand))
230+
{
231+
return await DisassembleWithFixedCommandAsync(fixedCommand, dotNetAssemblyfileAbsolutePath);
232+
}
219233

220-
foreach (var candidateDisassembleCommand in CandidateDisassembleCommands())
234+
await _disassemblerSelectionLock.WaitAsync().ConfigureAwait(false);
235+
try
221236
{
222-
// 直近で連続失敗したコマンドは一時的にブラックリスト化しているためスキップ。
223-
if (IsDisassemblerBlacklisted(candidateDisassembleCommand))
237+
fixedCommand = _fixedDisassembleCommand;
238+
if (!string.IsNullOrWhiteSpace(fixedCommand))
224239
{
225-
continue;
240+
return await DisassembleWithFixedCommandAsync(fixedCommand, dotNetAssemblyfileAbsolutePath);
226241
}
227242

228-
try
243+
Exception lastError = null;
244+
foreach (var candidateDisassembleCommand in CandidateDisassembleCommands())
229245
{
230-
// キャッシュ確認とプロセス起動を内包した TryDisassembleAsync を実行。
231-
var (success, ilText, disassembleCommandAndItsVersionWithArguments, error) = await TryDisassembleAsync(candidateDisassembleCommand, dotNetAssemblyfileAbsolutePath);
232-
if (success)
246+
// 直近で連続失敗したコマンドは一時的にブラックリスト化しているためスキップ。
247+
if (IsDisassemblerBlacklisted(candidateDisassembleCommand))
233248
{
234-
return (ilText, disassembleCommandAndItsVersionWithArguments);
249+
continue;
235250
}
236-
if (error != null)
251+
252+
try
237253
{
238-
lastError = error;
254+
// キャッシュ確認とプロセス起動を内包した TryDisassembleAsync を実行。
255+
var (success, ilText, disassembleCommandAndItsVersionWithArguments, error) = await TryDisassembleAsync(candidateDisassembleCommand, dotNetAssemblyfileAbsolutePath, allowCache: false);
256+
if (success)
257+
{
258+
Volatile.Write(ref _fixedDisassembleCommand, candidateDisassembleCommand);
259+
return (ilText, disassembleCommandAndItsVersionWithArguments);
260+
}
261+
if (error != null)
262+
{
263+
lastError = error;
264+
}
265+
}
266+
catch (System.ComponentModel.Win32Exception ex)
267+
{
268+
lastError = ex;
269+
LoggerService.LogMessage(LoggerService.LogLevel.Warning, string.Format(LOG_FAILED_TO_START_DISASSEMBLER, candidateDisassembleCommand, ex.Message), shouldOutputMessageToConsole: true, ex);
270+
RegisterDisassembleFailure(candidateDisassembleCommand);
271+
continue;
272+
}
273+
catch (Exception ex)
274+
{
275+
lastError = ex;
276+
LoggerService.LogMessage(LoggerService.LogLevel.Warning, string.Format(LOG_UNEXPECTED_ERROR_PREPARING_DISASSEMBLER, candidateDisassembleCommand, ex.Message), shouldOutputMessageToConsole: true, ex);
277+
RegisterDisassembleFailure(candidateDisassembleCommand);
278+
continue;
239279
}
240280
}
241-
catch (System.ComponentModel.Win32Exception ex)
281+
282+
var innerMsg = lastError != null ? string.Format(INFO_ROOT_CAUSE_FORMAT, lastError.Message) : string.Empty;
283+
throw new InvalidOperationException(string.Format(ERROR_EXECUTE_ILDASM, dotNetAssemblyfileAbsolutePath, GUIDANCE_INSTALL_DISASSEMBLER, innerMsg), lastError);
284+
}
285+
finally
286+
{
287+
_disassemblerSelectionLock.Release();
288+
}
289+
}
290+
291+
/// <summary>
292+
/// 固定済みの逆アセンブラのみで実行します(混在防止)。
293+
/// </summary>
294+
private async Task<(string ilText, string commandString)> DisassembleWithFixedCommandAsync(string disassembleCommand, string dotNetAssemblyfileAbsolutePath)
295+
{
296+
Exception lastError = null;
297+
try
298+
{
299+
var (success, ilText, disassembleCommandAndItsVersionWithArguments, error) = await TryDisassembleAsync(disassembleCommand, dotNetAssemblyfileAbsolutePath, allowCache: true);
300+
if (success)
242301
{
243-
lastError = ex;
244-
LoggerService.LogMessage(LoggerService.LogLevel.Warning, string.Format(LOG_FAILED_TO_START_DISASSEMBLER, candidateDisassembleCommand, ex.Message), shouldOutputMessageToConsole: true, ex);
245-
RegisterDisassembleFailure(candidateDisassembleCommand);
246-
continue;
302+
return (ilText, disassembleCommandAndItsVersionWithArguments);
247303
}
248-
catch (Exception ex)
304+
if (error != null)
249305
{
250-
lastError = ex;
251-
LoggerService.LogMessage(LoggerService.LogLevel.Warning, string.Format(LOG_UNEXPECTED_ERROR_PREPARING_DISASSEMBLER, candidateDisassembleCommand, ex.Message), shouldOutputMessageToConsole: true, ex);
252-
RegisterDisassembleFailure(candidateDisassembleCommand);
253-
continue;
306+
lastError = error;
254307
}
255308
}
309+
catch (System.ComponentModel.Win32Exception ex)
310+
{
311+
lastError = ex;
312+
LoggerService.LogMessage(LoggerService.LogLevel.Warning, string.Format(LOG_FAILED_TO_START_DISASSEMBLER, disassembleCommand, ex.Message), shouldOutputMessageToConsole: true, ex);
313+
RegisterDisassembleFailure(disassembleCommand);
314+
}
315+
catch (Exception ex)
316+
{
317+
lastError = ex;
318+
LoggerService.LogMessage(LoggerService.LogLevel.Warning, string.Format(LOG_UNEXPECTED_ERROR_PREPARING_DISASSEMBLER, disassembleCommand, ex.Message), shouldOutputMessageToConsole: true, ex);
319+
RegisterDisassembleFailure(disassembleCommand);
320+
}
256321

257322
var innerMsg = lastError != null ? string.Format(INFO_ROOT_CAUSE_FORMAT, lastError.Message) : string.Empty;
258323
throw new InvalidOperationException(string.Format(ERROR_EXECUTE_ILDASM, dotNetAssemblyfileAbsolutePath, GUIDANCE_INSTALL_DISASSEMBLER, innerMsg), lastError);
@@ -398,7 +463,10 @@ public async Task PrefetchIlCacheAsync(IEnumerable<string> dotNetAssemblyFilesAb
398463
/// <param name="disassembleCommand">使用するコマンド(ildasm / dotnet / ilspycmd など)</param>
399464
/// <param name="dotNetAssemblyFileAbsolutePath">対象アセンブリの絶対パス</param>
400465
/// <returns>成功可否、IL テキスト、ツールラベル、発生した例外</returns>
401-
private async Task<(bool Success, string IlText, string DisassembleCommandAndItsVersionWithArguments, Exception Error)> TryDisassembleAsync(string disassembleCommand, string dotNetAssemblyFileAbsolutePath)
466+
private async Task<(bool Success, string IlText, string DisassembleCommandAndItsVersionWithArguments, Exception Error)> TryDisassembleAsync(
467+
string disassembleCommand,
468+
string dotNetAssemblyFileAbsolutePath,
469+
bool allowCache)
402470
{
403471
Exception lastError = null;
404472
string tempAsciiPath = CreateAsciiTempCopyIfNeeded(dotNetAssemblyFileAbsolutePath);
@@ -407,7 +475,7 @@ public async Task PrefetchIlCacheAsync(IEnumerable<string> dotNetAssemblyFilesAb
407475
{
408476
foreach (var argset in BuildArgSets(disassembleCommand, dotNetAssemblyFileAbsolutePath, tempAsciiPath))
409477
{
410-
var (success, ilText, disassembleCommandAndItsVersionWithArguments, error) = await TryDisassembleWithArguments(disassembleCommand, dotNetAssemblyFileAbsolutePath, argset);
478+
var (success, ilText, disassembleCommandAndItsVersionWithArguments, error) = await TryDisassembleWithArguments(disassembleCommand, dotNetAssemblyFileAbsolutePath, argset, allowCache);
411479
if (success)
412480
{
413481
return (success, ilText, disassembleCommandAndItsVersionWithArguments, error);
@@ -435,13 +503,14 @@ public async Task PrefetchIlCacheAsync(IEnumerable<string> dotNetAssemblyFilesAb
435503
private async Task<(bool Success, string IlText, string DisassembleCommandAndItsVersionWithArguments, Exception Error)> TryDisassembleWithArguments(
436504
string disassembleCommand,
437505
string dotNetAssemblyFileAbsolutePath,
438-
(string workingDirectory, string[] args, string tempOut) argset)
506+
(string workingDirectory, string[] args, string tempOut) argset,
507+
bool allowCache)
439508
{
440509
string disassembleCommandAndItsVersionWithArguments = null;
441510
string ilText = null;
442511

443512
// 逆アセンブル結果の取得前に IL キャッシュを確認してヒットすればプロセス起動を省略する。
444-
if (_config.EnableILCache && _ilCache != null)
513+
if (allowCache && _config.EnableILCache && _ilCache != null)
445514
{
446515
try
447516
{

0 commit comments

Comments
 (0)