Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/target
*.bndb
*.bndb
/toolchain
/research
136 changes: 135 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,139 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.4.0] - 2026-10-08

P-Code analysis: the opcode table covers every handler of `MSVBVM60.DLL`
6.00.8176 and 6.00.9848, and new APIs give each instruction's stack effect,
call target and signature, a stack simulation over a procedure, and the
interfaces of the objects in its frame. Form data, hosted controls and many
structure readers were corrected against compiled fixtures.

### Added

- **Stack effects** (`pcode::stackeffect`, `pcode::movement`):
`Instruction::{stack_effect, pr_source, movement, procedure_return,
value_type, frame_slots, jump_targets}`: what an instruction pops and
pushes on the evaluation and x87 stacks, where Pr comes from, what a load
or store moves and its side effects, and how an `ExitProc*` returns.
- **Stack simulation** (`pcode::stacksim`): `ProcedureStack::simulate`
gives the stacks at every instruction over the control flow, including
`GoSub` depths (`shared_gosub_code`) and paths cut at unknown callees.
- **Call resolution** (`pcode::calltarget`): `CallResolver`, `CallSignature`
and `Callee` name the callee and argument widths of every call opcode:
project procedures through vtables built from method link tables (forms
and UserControls after their designer interface and control getters),
`Declare`s, runtime imports, control getters, variable accessors,
`RaiseEvent`, late-bound members and external interfaces.
- **External interfaces**: `InterfaceCatalog`, `InterfaceMember` (with
`returns_interface`) and `CallResolver::with_interfaces` let a host supply
type library members; `RuntimeInterfaces` covers the runtime's own
(control arrays, `Err`).
- **Object slot typing**: `SlotInterfaces`, `ProjectSlots` and
`CallResolver::{resolve_with_slots, simulate_procedure,
simulate_procedure_seeded, infer_project_slots, returned_interface}` infer
the interface of the object in each frame slot from casts, typed calls,
call results and arguments across procedures, so typed `VCall*` without an
IID resolve.
- **Opcode table**: columns `stack`, `popped`, `object`, `movement` and
per-build handler addresses; `OpcodeInfo::{popped, rule_at, fpu_callee,
pr_load, stack, receiver, handler, value_type}`, `StackRule`, `Receiver`;
`OpcodeSemantics::{GoSub, GoSubReturn, Resume, OnError, Raise, End}`;
`CallKind::Event`, `ArgumentOrder`; `Operand::{JumpTable, FrameList}`.
- **Procedures**: `ProcDscInfo::{zeroed_frame, line_numbers,
line_table_offset}`, `PCodeMethod::error_handling`,
`FrameResolver::{for_method, declarations}`, `FrameVar::{ReturnValue,
HiddenResult}`, `FrameOwner`.
- **Constant pool and imports**: `ConstantPool::{entry_at, entries, va_at,
string_at, name_at, guid_at, api_stub_at}` and `PoolEntry`;
`imports::ImportTable` (`VbProject::imports`).
- **Objects**: `vb::designer::Designer`, `VbObject::{designer, object_kind,
static_bytes, instancing}`, `Instancing`,
`ObjectTypeFlags::{is_exposed, is_global_namespace}`,
`VbProject::com_registration`, `MethodLink::kind`, `MethodEntry::Declare`,
`OptionalObjectInfo::basic_class_object_size`, `proc_dsc_va` on
`CodeEntry` and `CodeEntrypoint`, `ProjectInfo2Iter`, `ProjectData::path`,
`GuiTableEntry::object_index`, `ExternalDeclareInfo::{ordinal,
is_by_ordinal, resolve_cache_va}`.
- **Hosted ActiveX controls**: `ExternalComponentEntry::{clsid,
events_iid, default_iid, instance_events_iid, array_events_iid,
declared_event_count, license_key, event_dispids, event_conversions,
bindable_properties, extender_flags, control_flags}`, `BindableProperty`,
`ParamConversion`, `ConversionKind`; extender events are named.
- **Form data**: `FormDataParser::{form_name, form_type}`,
`FormControlRecord::has_submenu`, `Property::index`,
`PropertyIter::{position, at_terminator}`, `PropertyValue::{Single,
Bounds, Scale}`, `ControlBounds`, `ScaleState`, `UserScale`.
- `impl FromStr for Guid` and `Error::InvalidGuid`.
- 34 runtime exports missing from the export table.
- Compiled test fixtures covering classes, forms, MDI, controls, ActiveX
DLL/OCX/EXE projects, hosted ActiveX controls, every intrinsic control
property, native builds and opcode coverage, with a Docker/Wine build and
test suites for the stack simulation, call resolution and structures.

### Changed

- Opcode table stack effects: a Variant on the evaluation stack is its
address (one slot); `Mem*`, `VCall*` and `Late*` take their object from
Pr and the Pr loaders push nothing; the x87 result of `ImpAdCallFPR4`,
`VCallFPR8` and `ThisVCall*` is the callee's.
- `FuncTypDesc` reads the entry count, property kind, `ParamArray` flag and
type list as the compiler writes them; `ArgType` is a struct.
- Every P-Code procedure's arguments start at `ebp+0x0C`
(`pcode_frame::FIRST_ARG`); `FrameResolver` names `ebp+8` `me` or
`module_data`.
- `ProcOptFlags::{FRIEND, ADJUSTED_ME_AS_PRIMARY}` replace
`HAS_ERROR_HANDLER` / `HAS_RESUME_NEXT`.
- Renamed: `Callee::Api` to `Declare`; `StackEffect::writes_pr` to `pr`;
`PrivateObjectDescriptor::{func_count, func_count2, var_count,
desc_size}` to `member_count`, `event_count`, `var_stub_count`,
`instance_size`; `VbObject::class_form_public_bytes` to `public_bytes`
(covers modules); `OptionalObjectInfo::pcode_count_raw` to
`inherited_vtable_slots`; `EventHandlerThunk::{event_dispatch_id,
return_handler_va}` to `this_adjust`, `engine_thunk_va`;
`PropType::LongPair` to `Bounds`.
- An unknown property index is named `"?"` (with `Property::index`)
instead of a leaked string.
- `examples/dump` annotates calls with their callee and effect.

### Removed

- `ControlPosition` / `PropertyValue::Position` (now `ControlBounds`),
`decode_form_type`, `MethodLink::this_adjust`, `OpcodeInfo::{mem_read,
mem_write}` (now `movement`), `ClassFormPublicBytes::{default_iid,
events_iid}`, `eventname::standard_event_name`.
- `ExternalComponentEntry::{info_block_size, event_count, event_names}`:
the fields are the licence key length and bindable properties.
- `ConstantPool`'s byte-offset accessors and `ConstPoolEntry`,
`ImportResolver` / `CallTarget`, `PublicVarTable` and its iterator,
`OptionalObjectInfo::{pcode_count, is_native_sentinel}`,
`ExternalDeclareInfo::{api_stub, native_stub_va}`.

### Fixed

- Opcode table rows (arities of `ForVar`, `ForStepVar`, `PutMem8`,
`PutMemVar`, `VerifyVarObj`, the `Next*` and `ExitProcCb*` forms;
operand kinds of `ParmAry1St`, `WMemSt*`, `LitVarStr`, `CStrVarVal`;
several instruction sizes) and runtime export signatures (`Rnd`,
`rtcDateAdd`, `rtcVarFromFormatVar`, `rtDecFromVar`, the `VBDll*`
exports).
- Late-bound member names are null-terminated UTF-16, not BSTRs.
- Form data: the designer's own record is no longer decoded as properties;
`ScaleMode` carries its scale and `AutoRedraw` / `FontTransparent` (a
PictureBox's misaligned the rest of its record); every menu after the
first was lost; control bounds, Singles and signed coordinates decode
correctly; an empty OLE container's `OleObjectBlob` has no data; 52
property names and the control GUID table follow the runtime.
- `GuiObjectType` codes, designer vtable bases, control getters of
controls without handlers, and the IDs and DISPIDs of hosted controls.
- `VbObject::{func_type_descs, has_pcode, events, methods, method_name}`,
module variable tables, `ControlTypeIter`, `MethodLinkIterator`,
`ExternalTableEntry::as_typelib` and the `ComRegObject` type tests.
- `code_entrypoints` resolves P-Code `Sub Main`; `code_entries` lists each
method once.
- Counts read from the file are capped at what the data can hold, and a
packer's `push imm32; ret` entry point is not taken for a VB6 header.

## [0.3.2] - 2026-09-13

### Added
Expand Down Expand Up @@ -360,7 +493,8 @@ Affects `vbobject.rs`, `pcodemethod.rs`, `methodlink.rs`,

Initial public release.

[Unreleased]: https://github.com/ATRAPSLLC/visualbasic-rs/compare/v0.3.2...HEAD
[Unreleased]: https://github.com/ATRAPSLLC/visualbasic-rs/compare/v0.4.0...HEAD
[0.4.0]: https://github.com/ATRAPSLLC/visualbasic-rs/compare/v0.3.2...v0.4.0
[0.3.2]: https://github.com/ATRAPSLLC/visualbasic-rs/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/ATRAPSLLC/visualbasic-rs/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/ATRAPSLLC/visualbasic-rs/compare/v0.2.1...v0.3.0
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "visualbasic"
version = "0.3.2"
version = "0.4.0"
edition = "2024"
rust-version = "1.88"
description = "Parse and inspect Visual Basic 6 compiled binaries"
Expand Down
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,15 @@ for obj in project.objects()? {
- **PE entry point** detection (EXE push-stub and DLL export patterns)
- **VBHeader**, **ProjectData**, **ObjectTable** and the full structure chain
- **PublicObjectDescriptor**, **ObjectInfo**, **OptionalObjectInfo**, **PrivateObjectDescriptor**
- **P-Code bytecode**: opcode tables, operand decoding, streaming instruction iterator
- **P-Code bytecode**: opcode tables, operand decoding, streaming instruction
iterator; each opcode's effect on the evaluation and x87 stacks and on Pr,
the object register, read from the runtime's handlers
- **Controls**: ControlInfo, event sink vtables, event handler thunks
- **COM metadata**: GUIDs, TypeLib registration, external component tables
- **Form binary data**: control trees, property streams, font/picture resources
- **MSVBVM60.DLL exports**: 169 runtime function signatures with parameter types
- **MSVBVM60.DLL exports**: every export by name and ordinal, with signatures
for most, so the runtime functions P-Code imports by ordinal are named
- **Imports**: the PE import table by import address table slot

## High-level walkers

Expand All @@ -73,6 +77,56 @@ each substructure by hand:
- [`VbProject::diagnostics()`] - eager parse-health probe surfacing
missing optional structures and known-anomaly patterns.

## Analysing P-Code

The `pcode` module documents the interpreter (dispatch, the two stacks, Pr,
the frame, calling conventions) and describes each instruction's runtime
effect, as the handlers of `MSVBVM60.DLL` implement it:

```rust,no_run
use visualbasic::{
VbProject,
pcode::{calltarget::CallResolver, decoder::Instruction, stacksim::ProcedureStack},
project::MethodEntry,
};

# fn main() -> Result<(), Box<dyn std::error::Error>> {
let bytes = std::fs::read("sample.exe")?;
let project = VbProject::from_bytes(&bytes)?;
let calls = CallResolver::new(&project)?;
for (object_index, object) in project.objects()?.enumerate() {
let object_index = u16::try_from(object_index)?;
for entry in object?.methods()? {
let Ok(MethodEntry::PCode(method)) = entry else { continue };
let code: Vec<Instruction> = method.instructions()?.collect::<Result<_, _>>()?;
// Each instruction's effect: values popped (last operand first, from
// both stacks), the value pushed, the receiver, Pr's source.
for insn in &code {
let effect = insn.stack_effect(calls.resolve(object_index, insn).as_ref());
println!("{insn} {effect:?}");
}
// The stacks over the procedure's control flow, every value
// resolved to the width its producer pushed.
let stack = ProcedureStack::simulate(&code, method.pcode_bytes(), &|insn, pr| {
calls.resolve_with_pr(object_index, insn, pr)
});
for (index, cut) in stack.cuts() {
println!("path cut at {:04x}: {cut:?}", code[index].offset);
}
}
}
# Ok(())
# }
```

- [`CallResolver`] gives each call's callee (a project procedure, a
`Declare` function, an imported runtime function, an external interface by
IID and vtable offset, a late-bound member) and signature.
- [`FrameResolver`] names `%a` frame offsets: arguments, locals, the
interpreter's slots.
- [`ConstantPool`] reads pool entries by the index `%s` / `%c` operands
carry.

## Cargo features

| Feature | Default | Effect |
Expand Down
Loading
Loading