Skip to content

Commit

Permalink
Feature: allow user to specify search areas (#1259)
Browse files Browse the repository at this point in the history
  • Loading branch information
uxmal committed Sep 29, 2023
1 parent ff466b9 commit 865715f
Show file tree
Hide file tree
Showing 31 changed files with 2,612 additions and 4,633 deletions.
41 changes: 33 additions & 8 deletions src/Decompiler/Scanning/StringFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,8 @@ public StringFinder(Program program)

public IEnumerable<AddressSearchHit> FindStrings(StringFinderCriteria criteria)
{
foreach (var segment in program.SegmentMap.Segments.Values)
foreach (var rdr in GenerateReaders(program, criteria))
{
if (segment.MemoryArea is not ByteMemoryArea mem)
continue; //$TODO: what to do with odd archs?
Address segEnd = Address.Min(
segment.Address + segment.Size,
segment.MemoryArea.BaseAddress + mem.Bytes.Length);
var rdr = criteria.CreateReader(mem, segment.Address, segEnd);
Address? addrStartRun = null;
int cValid = 0;
var charType = (PrimitiveType)criteria.StringType.ElementType;
Expand All @@ -76,6 +70,36 @@ public IEnumerable<AddressSearchHit> FindStrings(StringFinderCriteria criteria)
}
}

private IEnumerable<EndianImageReader> GenerateReaders(Program program, StringFinderCriteria criteria)
{
if (criteria.Areas is null || criteria.Areas.Count == 0)
{
foreach (var segment in program.SegmentMap.Segments.Values)
{
if (segment.MemoryArea is not ByteMemoryArea mem)
continue; //$TODO: what to do with odd archs?
Address segEnd = Address.Min(
segment.Address + segment.Size,
segment.MemoryArea.BaseAddress + mem.Bytes.Length);
var rdr = criteria.CreateReader(mem, segment.Address, segment.Size);
yield return rdr;
}
}
else
{
foreach (var area in criteria.Areas)
{
if (area.Program.SegmentMap.TryFindSegment(area.Address, out var segment) &&
segment.MemoryArea is ByteMemoryArea mem)
{
var rdr = criteria.CreateReader(mem, area.Address, area.Length);
yield return rdr;
}
}
}
}


//$TODO: This assumes only ASCII values are valid.
// How to deal with Swedish? Cyrillic? Chinese?
// Added common escaped characters for C strings.
Expand All @@ -89,5 +113,6 @@ public record StringFinderCriteria(
StringType StringType,
Encoding Encoding,
int MinimumLength,
Func<ByteMemoryArea, Address, Address, EndianImageReader> CreateReader);
List<ProgramAddressRange>? Areas, // Optional search areas; null means search entire program image.
Func<ByteMemoryArea, Address, long, EndianImageReader> CreateReader);
}
1 change: 1 addition & 0 deletions src/Gui/AddressSearchResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ public async virtual ValueTask<bool> ExecuteAsync(CommandID cmdID)
StringType.NullTerminated(PrimitiveType.Char),
Encoding.ASCII,
0,
null,
default!));
View.Invalidate(); break;
case CmdIds.ViewAsData: details = new DataSearchDetails(); View.Invalidate(); break;
Expand Down
26 changes: 16 additions & 10 deletions src/Gui/Forms/MainFormInteractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,12 @@ private void SelectProgram(Program? program = null)
svc.SelectedProgram = program;
}

private Program? SelectedProgram()
{
var svc = Services.RequireService<ISelectedAddressService>();
return svc.SelectedProgram;
}

/// <summary>
/// Prompts the user for a metadata file and adds to the project.
/// </summary>
Expand Down Expand Up @@ -721,17 +727,17 @@ public void FindProcedures(ISearchResultService svc)

public async ValueTask FindStrings(ISearchResultService srSvc)
{
using (var dlgStrings = dlgFactory.CreateFindStringDialog())
var program = SelectedProgram();
if (program is null)
return;
using var dlgStrings = dlgFactory.CreateFindStringDialog(program);
var criteria = await uiSvc.ShowModalDialog(dlgStrings);
if (criteria is not null)
{
var criteria = await uiSvc.ShowModalDialog(dlgStrings);
if (criteria is not null)
{
var hits = this.decompilerSvc.Decompiler!.Project.Programs
.SelectMany(p => new StringFinder(p).FindStrings(criteria));
srSvc.ShowAddressSearchResults(
hits,
new StringSearchDetails(criteria));
}
var hits = new StringFinder(program).FindStrings(criteria);
srSvc.ShowAddressSearchResults(
hits,
new StringSearchDetails(criteria));
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Gui/IDialogFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public interface IDialogFactory
IAssumedRegisterValuesDialog CreateAssumedRegisterValuesDialog(IProcessorArchitecture arch);
IAddressPromptDialog CreateAddressPromptDialog();
ICallSiteDialog CreateCallSiteDialog(Program program, UserCallData ucd);
IDialog<StringFinderCriteria?> CreateFindStringDialog();
IDialog<StringFinderCriteria?> CreateFindStringDialog(Program program);
IKeyBindingsDialog CreateKeyBindingsDialog(Dictionary<string, Dictionary<int, CommandID>> keyBindings);
IDialog<LoadDetails?> CreateOpenAsDialog(string initialFilename);
IDialog<UserProcedure?> CreateProcedureDialog(Program program, UserProcedure proc);
Expand Down
15 changes: 14 additions & 1 deletion src/Gui/Services/FileSystemSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ public override void Delete(string name)

public override object? Get(string settingName, object? defaultValue)
{
if (!this.settings.TryGetValue(settingName, out var value))
if (string.IsNullOrEmpty(settingName) ||
!this.settings.TryGetValue(settingName, out var value))
return defaultValue;
if (value is JsonElement je)
{
Expand All @@ -67,6 +68,18 @@ public override void Delete(string name)

public override string[] GetList(string settingName)
{
if (string.IsNullOrEmpty(settingName) ||
!this.settings.TryGetValue(settingName, out var value))
return Array.Empty<string>();
if (value is JsonElement je && je.ValueKind == JsonValueKind.Array)
{
var result = new String[je.GetArrayLength()];
for (int i = 0; i < result.Length; ++i)
{
result[i] = je[i].GetString()!;
}
return result;
}
throw new NotImplementedException();
}

Expand Down
118 changes: 116 additions & 2 deletions src/Gui/ViewModels/Dialogs/FindStringsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,36 @@
using Reko.Core.Memory;
using Reko.Core.Types;
using Reko.Gui.Reactive;
using Reko.Gui.Services;
using Reko.Scanning;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Reko.Gui.ViewModels.Dialogs
{
public class FindStringsViewModel : ChangeNotifyingObject
{
public FindStringsViewModel()
private Program program;
private IDecompilerShellUiService uiSvc;
private ISettingsService settingsSvc;
private IDialogFactory dialogFactory;

public FindStringsViewModel(
Program program,
IDecompilerShellUiService uiSvc,
ISettingsService settingsSvc,
IDialogFactory dialogFactory)
{
this.program = program;
this.uiSvc = uiSvc;
this.settingsSvc = settingsSvc;
this.dialogFactory = dialogFactory;
this.minLength = 5;
this.program = program;
this.SearchAreasMru = LoadMruFromSettings();
}

public int CharacterType
Expand All @@ -56,6 +75,15 @@ public int MinLength
}
private int minLength;

public ObservableCollection<ListOption> SearchAreasMru { get; }

public int SelectedMruIndex
{
get => selectedMruIndex;
set => base.RaiseAndSetIfChanged(ref selectedMruIndex, value);
}
private int selectedMruIndex;

public StringFinderCriteria GetCriteria()
{
return GetCriteria(this.CharacterType, this.StringKind, this.MinLength);
Expand All @@ -65,7 +93,7 @@ public StringFinderCriteria GetCriteria(int characterType, int stringKind, int m
{
Encoding encoding;
PrimitiveType charType;
Func<ByteMemoryArea, Address, Address, EndianImageReader> rdrCreator;
Func<ByteMemoryArea, Address, long, EndianImageReader> rdrCreator;
switch (characterType)
{
default:
Expand Down Expand Up @@ -93,11 +121,97 @@ public StringFinderCriteria GetCriteria(int characterType, int stringKind, int m
case 2: case 3: strType = StringType.LengthPrefixedStringType(charType, PrimitiveType.UInt16); break;
}

var searchAreasListOption = this.SearchAreasMru[this.SelectedMruIndex];
var searchAreas = (searchAreasListOption.Value is SearchArea sa && sa.Areas.Count != 0)
? sa.Areas
: null;
return new StringFinderCriteria(
StringType: strType,
Encoding: encoding,
Areas: searchAreas,
MinimumLength: minLength,
CreateReader: rdrCreator);
}

public async ValueTask SelectSearchArea()
{
var dlg = this.dialogFactory.CreateSearchAreaDialog(program, new SearchArea());
var searchArea = await uiSvc.ShowModalDialog(dlg);
if (searchArea is null || searchArea.Areas.Count == 0)
return;
UpdateSearchAreaMru(searchArea);
}

public void UpdateSearchAreaMru(SearchArea? searchArea)
{
var iExisting = IndexOf(this.SearchAreasMru, e => object.Equals(e.Value, searchArea));
ListOption item;
if (iExisting != 0)
{
if (iExisting >= 1)
{
item = this.SearchAreasMru[iExisting];
this.SearchAreasMru.RemoveAt(iExisting);
}
else
{
item = new ListOption(searchArea?.ToString() ?? "", searchArea);
}
this.SearchAreasMru.Insert(0, item);
}
this.selectedMruIndex = -1; // Hack to force an event to be raised.
this.SelectedMruIndex = 0;
}

private static int IndexOf<T>(ObservableCollection<T> list, Predicate<T> predicate)
{
for (int i = 0; i < list.Count; ++i)
{
if (predicate(list[i]))
return i;
}
return -1;
}


public ObservableCollection<ListOption> LoadMruFromSettings()
{
var items = settingsSvc.GetList("FindStringsDialog/AreasMru");
if (items is null || items.Length == 0)
{
return new ObservableCollection<ListOption>
{
new ListOption("Entire program", null)
};
}
else
{
var list = new ObservableCollection<ListOption>();
foreach (string s in items)
{
ListOption item;
if (string.IsNullOrEmpty(s))
{
item = new ListOption("Entire program", null);
list.Add(item);
}
else
{
if (SearchArea.TryParse(program, s, out var sa))
{
item = new ListOption(s, sa);
list.Add(item);
}
}
}
return list;
}
}

public void SaveMruToSettings()
{
settingsSvc.SetList("FindStringsDialog/AreasMru", SearchAreasMru
.Select(a => a.Value is null ? "" : a.Text));
}
}
}

0 comments on commit 865715f

Please sign in to comment.