aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_project.py (follow)
Commit message (Collapse)AuthorAgeFilesLines
* loading: say how to read a headerless blob (processor, base)blasty14 hours1-0/+40
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A raw firmware dump has no format to detect, so IDA fell back to x86 at address 0. It doesn't fail — it opens, analyses, and finds nothing. An AArch64 image loaded this way gave 0 functions; told the truth it gives 35. ida-tui fw.bin --processor arm --base 0x8000000 and per binary in a project, which is what a multi-image firmware actually needs: {"path": "app.bin", "processor": "arm", "base": "0x8000000"} idapro.open_database() already accepted IDA command-line switches; nothing was passing any. Plumbed BinaryRef -> WorkerPool -> WorkerClient -> worker argv, plus a load_args for the single-binary path that has no project ref. base is written the way people say it (0x8000000, int or string, any base). IDA's -b is in PARAGRAPHS — -b1000 loads at 0x10000 — so BinaryRef.load_args converts, and a base that isn't 16-byte aligned is refused rather than silently landing 16x off. ida_args passes anything else through. Two bugs found by testing the whole path rather than the happy one: * Project.load() whitelisted path/label when normalising entries, so the load options were dropped the first time a project was reopened — set a processor, come back tomorrow, it's gone. * Re-passing the switches to an EXISTING database makes IDA refuse the open (rc != 0, no functions). The .i64 already records how the image was loaded, so the worker skips them once a database exists. My first guard checked splitext(path) + ".i64" and never fired, because IDA names it "<file>.i64" — keeping the extension. It checks both spellings now. Verified on a real AArch64 blob: fresh load 35 functions based at 0x8002440, reopen 35 again, and the CLI rejects an unaligned or non-numeric --base. tests: +6 project (options recorded, paragraph conversion, file round-trip, add() takes them, an ELF passes nothing, hex-string base). 39/0 project, 195/0 scenarios, 30/0 project UI, 36/0 index, 27/0 pool.
* projects: don't duplicate a binary that's already in the projectblasty21 hours1-0/+35
| | | | | | | | | | | | | | | | | | | | | | | | | | | | `--project fw.json a.elf b.elf` where those are already listed appended them again, so the project grew a second copy on every launch — each duplicate then staged its own file, opened its own worker and got its own index entries. Dedupe on the RESOLVED SOURCE PATH, which is the only identity that's actually correct here: two different foo.elf from different directories are different binaries and must both be accepted (the label disambiguator already gives them foo.elf and foo.elf_2), while ./a.elf, /abs/a.elf and a symlink to it are all the same file and must collapse to one entry. * Project.by_source(path) — lookup by realpath. * Project.add() returns the existing entry instead of appending a duplicate. * Project.create() drops repeats on one command line too. * launch.py reports what it did: "added N binary(ies)" / "N already in the project (matched by path) — left alone", and only rewrites the file when something actually changed. Not deduped in _build_refs on load: remove() maps refs to entries by index, so collapsing there would desync them, and a hand-edited duplicate still works (labels disambiguate). tests/test_project.py +8 checks: re-add is a no-op, so are a relative spelling, a messy ../ path and a symlink; a same-named file from another directory IS added and gets a distinct label; create() drops repeats. 33/33. Verified on the real CLI: re-running the reported command leaves the project at 3 entries.
* projects: project model + binary staging (phase 1a)blasty26 hours1-0/+158
First slice of multi-binary projects (docs/PROJECTS.md): the on-disk model, with no runtime wiring yet. idatui/project.py (stdlib-only, like domain/worker): * Project.load/create/save — an explicit JSON project file listing binaries; paths resolve relative to it, labels default to the basename and are disambiguated on collision (they name files). * A sidecar dir beside the project file (<stem>.idatui.d/) holds bin/ (staged binaries), their .i64 + scratch, and idx/ for phase 2. IDA opens the STAGED file, so nothing lands in the source tree — today targets/ carries ~244MB of IDA litter around ~13MB of binaries, much of it stale wedge files. * stage() copies rather than hardlinks. A hardlink is free but makes source and staged one inode, so an in-place rebuild (cp over the path truncates instead of replacing) would silently swap the bytes under an analysed DB with nothing to detect it. The unit test caught exactly that. A copy also leaves the sidecar self-contained once the sources are gone. * Re-staging a changed source drops its now-stale DB; sweep_scratch() clears the unpacked working files a hard-killed worker leaves behind (never the .i64). tests/test_project.py: 27 checks, pure stdlib (no IDA/textual/worker), <1s. docs/PROJECTS.md: the full design — the one-worker-per-DB constraint with measured costs (bash worker = 126MB RSS/117MB PSS; libcrypto's DB is 72MB, so residency is budgeted by MEMORY, not a worker count), the switch between "switching needs a live worker" and "searching doesn't (cached index)", and phases 1-4.