Task 625: .RSM File Format
Task 625: .RSM File Format
1. List of Properties of the .RSM File Format Intrinsic to Its Structure
The .RSM file format refers to the Remote Symbol Map file used by Embarcadero's RAD Studio (Delphi/C++Builder) for remote debugging purposes. It is based on Microsoft's CodeView symbolic debug information format (version 4 or 5), which stores symbol tables, type information, and line number mappings for debugger use. The format is binary, with variable-length records, natural alignment for fields (e.g., fields start on boundaries matching their size), and support for lexical scopes in programs. It is typically generated by the linker when the "Include remote debug symbols" option is enabled in project settings.
The intrinsic properties of the format, derived from the CodeView specification, are as follows:
- Version Signature: A 4-byte identifier at the file base (lfaBase), indicating the format version and packing status (e.g., "NB05" for unpacked CodeView 5, "NB09" for packed CodeView 5). Values starting with "NB" denote CodeView; mismatched signatures between symbols and types invalidate the file.
- Base Offset (lfaBase): The reference point for all offsets in the file, typically the start of the debug data section.
- Directory Offset (lfoDir): A 4-byte offset from lfaBase to the subsection directory.
- Directory Header: A fixed 20-byte structure containing:
- cbDirHeader (uint16): Header length (always 20).
- cbDirEntry (uint16): Entry length (always 12).
- cDir (uint16): Number of directory entries.
- lfoNextDir (uint32): Offset to the next directory (usually 0).
- flags (uint32): Reserved flags for future use.
- Subsection Directory Entries: An array of cDir entries (12 bytes each), each describing a subsection:
- subsection (uint16): Type identifier (e.g., 0x0120 for sstModule, 0x0121 for sstTypes).
- iMod (uint16): Module index (1-based; 0xFFFF for global subsections).
- lfo (uint32): Offset from lfaBase to the subsection data.
- cb (uint32): Size of the subsection in bytes.
- Subsections: Variable-length sections for specific data, aligned to 4-byte boundaries. Key types include:
- sstModule (0x0120): Module information (overlay number, library index, segment count, style string, per-segment details like offset and size).
- sstTypes (0x0121): Type definitions as a stream of records (see Type Records below).
- sstPublic/sstPublicSym (0x0122/0x0123): Public symbols (offset, segment, type index, name).
- sstSymbols/sstAlignSym (0x0124/0x0125): Symbol stream with padding for alignment.
- sstSrcModule (0x0127): Source line mappings (file count, segment count, per-file tables with line/offset pairs).
- sstLibraries (0x0128): Global library names (length-prefixed strings).
- sstGlobalSym/sstGlobalPub/sstGlobalTypes (0x0129/0x012A/0x012B): Compacted global symbols, publics, and types with optional hash tables for lookup.
- sstSegMap (0x012D): Segment mappings (logical to physical, with flags for read/write/execute).
- sstSegName (0x012E): Segment and class names (zero-terminated strings).
- sstPreComp (0x012F): Precompiled type headers with signatures.
- sstFileIndex (0x0133): Source file indices (module count, reference count, name offsets).
- sstStaticSym (0x0134): Static symbols (similar to global symbols).
- Type Records: Variable-length records in sstTypes/sstGlobalTypes, referenced by 16-bit indices (0x1000+ for non-primitives; 0x0000-0x0FFF for predefined primitives). Structure:
- Length (uint16): Record length (excludes length field).
- Leaf Index (uint16): Type code (e.g., LF_STRUCTURE = 0x0003 for structs, LF_POINTER = 0x0002 for pointers).
- Data: Variable, using leaf substructures (e.g., LF_FIELDLIST for field lists, LF_ARGLIST for argument lists) and numeric leaves (0x8000+ for values like LF_CHAR = 0x8000).
- Predefined Primitives: Bitfield-encoded (e.g., T_INT2 = 0x0009 for signed 16-bit int, T_REAL64 = 0x0043 for double).
- Symbol Records: Variable-length records in sstSymbols/sstGlobalSym, for names, addresses, and types. Structure:
- Length (uint16): Record length (excludes length field).
- Leaf Index (uint16): Symbol type (e.g., S_GPROC32 = 0x0214 for global procedure, S_LDATA32 = 0x0212 for local data).
- Data: Variable, including address (segment:offset), type index, name (length-prefixed), flags (e.g., frame pointer omission).
- Hash Tables: Optional in global subsections for fast lookup (name hash with checksums, address sort table).
- Alignment and Padding: Fields aligned to their size; padding bytes (S_ALIGN records) for 4K or long word boundaries; numeric leaves for variable data (e.g., if value ≥ 0x8000, follow with formatted data).
- Lexical Scopes: Nested scopes with pParent, pNext, pEnd offsets for procedure/block hierarchies.
- Architecture Support: Symbols grouped by model (e.g., 16:16 segmented, 16:32 segmented, MIPS); register enumerations per architecture (e.g., Intel 80x86: EAX = 17).
These properties ensure the format supports debugging across modules, with optimization for size via CVPACK tool (duplicate removal, compaction).
2. Two Direct Download Links for .RSM Files
Public sample .RSM files are rare due to their generation during compilation for proprietary projects. However, the following links provide sample Delphi projects that, when compiled with remote debug symbols enabled (Project > Options > Delphi Compiler > Debugging > Include remote debug symbols), generate .RSM files alongside the executable:
- https://github.com/Embarcadero/RadStudio11Demos/raw/main/Object Pascal/VCL/AnimCtrlDemo/AnimCtrlDemo.zip (Compile to generate AnimCtrlDemo.rsm)
- https://github.com/Embarcadero/RadStudio11Demos/raw/main/Object Pascal/VCL/AddressBook/AddressBook.zip (Compile to generate AddressBook.rsm)
To extract the .RSM, compile the project in RAD Studio and locate the file in the output directory.
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .RSM Property Dump
The following is a self-contained HTML snippet for embedding in a Ghost blog post. It allows drag-and-drop of a .RSM file, parses the basic structure (signature, directory, subsections), and dumps the properties to the screen. Advanced parsing (e.g., full type/symbol records) is omitted for brevity; extend as needed.
Drag and drop a .RSM file here to dump its properties.
4. Python Class for .RSM File Handling
The following Python class opens a .RSM file, decodes the basic structure (signature, directory, subsections), prints properties to console, and supports writing a simple .RSM skeleton (signature and empty directory).
import struct
class RSMDecoder:
def __init__(self, filename=None):
self.filename = filename
self.data = None
self.properties = {}
def read(self):
with open(self.filename, 'rb') as f:
self.data = f.read()
if len(self.data) < 8:
raise ValueError("File too short for RSM header.")
pos = 0
self.properties['signature'] = struct.unpack_from('<4s', self.data, pos)[0].decode('ascii', errors='ignore')
pos += 4
self.properties['lfoDir'] = struct.unpack_from('<I', self.data, pos)[0]
pos = self.properties['lfoDir']
header = struct.unpack_from('<HHH I I', self.data, pos)
self.properties['directory'] = {
'cbDirHeader': header[0],
'cbDirEntry': header[1],
'cDir': header[2],
'lfoNextDir': header[3],
'flags': header[4]
}
pos += 12 # Skip to first entry (after cbDirHeader, cbDirEntry, cDir; lfoNextDir and flags are 8 bytes)
self.properties['subsections'] = []
subsection_names = {0x0120: 'sstModule', 0x0121: 'sstTypes', 0x0122: 'sstPublic', 0x0123: 'sstPublicSym', 0x0124: 'sstSymbols', 0x0127: 'sstSrcModule', 0x0128: 'sstLibraries', 0x0129: 'sstGlobalSym', 0x012a: 'sstGlobalPub', 0x012b: 'sstGlobalTypes', 0x012d: 'sstSegMap', 0x012e: 'sstSegName', 0x0133: 'sstFileIndex', 0x0134: 'sstStaticSym'}
for i in range(self.properties['directory']['cDir']):
entry = struct.unpack_from('<H H I I', self.data, pos)
subsection_type = subsection_names.get(entry[0], 'Unknown')
self.properties['subsections'].append({
'type': subsection_type,
'hex_type': f'0x{entry[0]:04x}',
'iMod': entry[1],
'lfo': entry[2],
'cb': entry[3]
})
pos += 12
self.print_properties()
def print_properties(self):
print("RSM File Properties:")
print(f"Signature: {self.properties['signature']}")
print(f"Directory Offset: {self.properties['lfoDir']}")
print("Directory Header:")
for k, v in self.properties['directory'].items():
print(f" - {k}: {v}")
print("Subsections:")
for sub in self.properties['subsections']:
print(f" - Type: {sub['type']} ({sub['hex_type']}), Module: {sub['iMod']}, Offset: {sub['lfo']}, Size: {sub['cb']}")
def write(self, output_filename):
# Simple skeleton write: signature 'NB05', empty directory with 1 dummy subsection
with open(output_filename, 'wb') as f:
f.write(b'NB05') # Signature
f.write(struct.pack('<I', 20)) # lfoDir (example position)
# Directory header
f.write(struct.pack('<HHHII', 20, 12, 1, 0, 0)) # Dummy entry
# Dummy subsection entry (sstModule)
f.write(struct.pack('<HHII', 0x0120, 0, 24, 0)) # Dummy
print(f"Wrote skeleton .RSM to {output_filename}")
# Usage
if __name__ == "__main__":
decoder = RSMDecoder('example.rsm')
decoder.read()
decoder.write('skeleton.rsm')
5. Java Class for .RSM File Handling
The following Java class uses java.nio for binary reading, decodes the structure, prints properties, and supports writing a skeleton file.
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.HashMap;
import java.util.Map;
public class RSMDecoder {
private String filename;
private ByteBuffer buffer;
private Map<String, Object> properties = new HashMap<>();
public RSMDecoder(String filename) {
this.filename = filename;
}
public void read() throws IOException {
try (FileChannel channel = FileChannel.open(Paths.get(filename), StandardOpenOption.READ)) {
buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
}
buffer.position(0);
byte[] sigBytes = new byte[4];
buffer.get(sigBytes);
properties.put("signature", new String(sigBytes));
int lfoDir = buffer.getInt();
properties.put("lfoDir", lfoDir);
buffer.position(lfoDir);
short cbDirHeader = buffer.getShort();
short cbDirEntry = buffer.getShort();
short cDir = buffer.getShort();
int lfoNextDir = buffer.getInt();
int flags = buffer.getInt();
properties.put("directory", Map.of(
"cbDirHeader", cbDirHeader,
"cbDirEntry", cbDirEntry,
"cDir", cDir,
"lfoNextDir", lfoNextDir,
"flags", flags
));
Map<Integer, String> subsectionNames = Map.of(
0x0120, "sstModule", 0x0121, "sstTypes", 0x0122, "sstPublic", 0x0123, "sstPublicSym",
0x0124, "sstSymbols", 0x0127, "sstSrcModule", 0x0128, "sstLibraries", 0x0129, "sstGlobalSym",
0x012a, "sstGlobalPub", 0x012b, "sstGlobalTypes", 0x012d, "sstSegMap", 0x012e, "sstSegName",
0x0133, "sstFileIndex", 0x0134, "sstStaticSym"
);
var subsections = new java.util.ArrayList<Map<String, Object>>();
for (int i = 0; i < cDir; i++) {
short subsection = buffer.getShort();
short iMod = buffer.getShort();
int lfo = buffer.getInt();
int cb = buffer.getInt();
String name = subsectionNames.getOrDefault((int) subsection, "Unknown");
subsections.add(Map.of(
"type", name,
"hex_type", String.format("0x%04x", subsection),
"iMod", iMod,
"lfo", lfo,
"cb", cb
));
}
properties.put("subsections", subsections);
printProperties();
}
public void printProperties() {
System.out.println("RSM File Properties:");
System.out.println("Signature: " + properties.get("signature"));
System.out.println("Directory Offset: " + properties.get("lfoDir"));
System.out.println("Directory Header:");
((Map<?, ?>) properties.get("directory")).forEach((k, v) -> System.out.println(" - " + k + ": " + v));
System.out.println("Subsections:");
((java.util.List<?>) properties.get("subsections")).forEach(sub -> {
System.out.println(" - Type: " + ((Map<?, ?>) sub).get("type") + " (" + ((Map<?, ?>) sub).get("hex_type") + "), Module: " + ((Map<?, ?>) sub).get("iMod") + ", Offset: " + ((Map<?, ?>) sub).get("lfo") + ", Size: " + ((Map<?, ?>) sub).get("cb"));
});
}
public void write(String outputFilename) throws IOException {
try (FileChannel channel = FileChannel.open(Paths.get(outputFilename), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
ByteBuffer buf = ByteBuffer.allocate(64);
buf.put("NB05".getBytes());
buf.putInt(20); // lfoDir
buf.putShort((short) 20);
buf.putShort((short) 12);
buf.putShort((short) 1);
buf.putInt(0);
buf.putInt(0);
buf.putShort((short) 0x0120); // Dummy sstModule
buf.putShort((short) 0);
buf.putInt(24);
buf.putInt(0);
buf.flip();
channel.write(buf);
}
System.out.println("Wrote skeleton .RSM to " + outputFilename);
}
public static void main(String[] args) throws IOException {
RSMDecoder decoder = new RSMDecoder("example.rsm");
decoder.read();
decoder.write("skeleton.rsm");
}
}
6. JavaScript Class for .RSM File Handling
The following JavaScript class (ES6) reads a .RSM file via File or fetch, decodes the structure using DataView, prints properties to console, and supports writing a skeleton file (using Blob for download).
class RSMDecoder {
constructor(filename = null) {
this.filename = filename;
this.properties = {};
}
async read(fileOrUrl) {
let arrayBuffer;
if (fileOrUrl instanceof File) {
arrayBuffer = await fileOrUrl.arrayBuffer();
} else {
const response = await fetch(fileOrUrl);
arrayBuffer = await response.arrayBuffer();
}
const dataView = new DataView(arrayBuffer);
let pos = 0;
const signature = String.fromCharCode(dataView.getUint8(pos), dataView.getUint8(pos + 1), dataView.getUint8(pos + 2), dataView.getUint8(pos + 3));
this.properties.signature = signature;
pos += 4;
const lfoDir = dataView.getUint32(pos, true);
this.properties.lfoDir = lfoDir;
pos = lfoDir;
const cbDirHeader = dataView.getUint16(pos, true);
const cbDirEntry = dataView.getUint16(pos + 2, true);
const cDir = dataView.getUint16(pos + 4, true);
const lfoNextDir = dataView.getUint32(pos + 6, true);
const flags = dataView.getUint32(pos + 10, true);
this.properties.directory = { cbDirHeader, cbDirEntry, cDir, lfoNextDir, flags };
pos += 20; // Header end
const subsectionNames = { 0x0120: 'sstModule', 0x0121: 'sstTypes', 0x0122: 'sstPublic', 0x0123: 'sstPublicSym', 0x0124: 'sstSymbols', 0x0127: 'sstSrcModule', 0x0128: 'sstLibraries', 0x0129: 'sstGlobalSym', 0x012a: 'sstGlobalPub', 0x012b: 'sstGlobalTypes', 0x012d: 'sstSegMap', 0x012e: 'sstSegName', 0x0133: 'sstFileIndex', 0x0134: 'sstStaticSym' };
this.properties.subsections = [];
for (let i = 0; i < cDir; i++) {
const subsection = dataView.getUint16(pos, true);
const iMod = dataView.getUint16(pos + 2, true);
const lfo = dataView.getUint32(pos + 4, true);
const cb = dataView.getUint32(pos + 8, true);
const name = subsectionNames[subsection] || 'Unknown';
this.properties.subsections.push({ type: name, hex_type: `0x${subsection.toString(16).padStart(4, '0')}`, iMod, lfo, cb });
pos += 12;
}
this.printProperties();
}
printProperties() {
console.log('RSM File Properties:');
console.log(`Signature: ${this.properties.signature}`);
console.log(`Directory Offset: ${this.properties.lfoDir}`);
console.log('Directory Header:');
Object.entries(this.properties.directory).forEach(([k, v]) => console.log(` - ${k}: ${v}`));
console.log('Subsections:');
this.properties.subsections.forEach(sub => {
console.log(` - Type: ${sub.type} (${sub.hex_type}), Module: ${sub.iMod}, Offset: ${sub.lfo}, Size: ${sub.cb}`);
});
}
write(outputFilename) {
const skeleton = new ArrayBuffer(48);
const view = new DataView(skeleton);
let pos = 0;
'NB05'.split('').forEach((char, i) => view.setUint8(pos + i, char.charCodeAt(0)));
pos += 4;
view.setUint32(pos, 20, true); // lfoDir
pos = 20;
view.setUint16(pos, 20, true); // cbDirHeader
pos += 2;
view.setUint16(pos, 12, true); // cbDirEntry
pos += 2;
view.setUint16(pos, 1, true); // cDir
pos += 2;
view.setUint32(pos, 0, true); // lfoNextDir
pos += 4;
view.setUint32(pos, 0, true); // flags
pos += 4;
view.setUint16(pos, 0x0120, true); // Dummy sstModule
pos += 2;
view.setUint16(pos, 0, true); // iMod
pos += 2;
view.setUint32(pos, 24, true); // lfo
pos += 4;
view.setUint32(pos, 0, true); // cb
const blob = new Blob([skeleton]);
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFilename;
a.click();
URL.revokeObjectURL(url);
console.log(`Wrote skeleton .RSM (download triggered): ${outputFilename}`);
}
}
// Usage (e.g., in browser console)
// const decoder = new RSMDecoder();
// decoder.read(fileInput.files[0]); // or decoder.read('example.rsm');
7. C Class for .RSM File Handling
The following C code (using stdio and stdlib) implements a simple struct-based decoder, reads the structure, prints properties, and writes a skeleton file. Compile with gcc rsm_decoder.c -o rsm_decoder.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
char signature[4];
uint32_t lfoDir;
uint16_t cbDirHeader;
uint16_t cbDirEntry;
uint16_t cDir;
uint32_t lfoNextDir;
uint32_t flags;
struct {
uint16_t type;
uint16_t iMod;
uint32_t lfo;
uint32_t cb;
} subsections[32]; // Assume max 32 subsections
} RSMProperties;
const char *get_subsection_name(uint16_t id) {
switch (id) {
case 0x0120: return "sstModule";
case 0x0121: return "sstTypes";
case 0x0122: return "sstPublic";
case 0x0123: return "sstPublicSym";
case 0x0124: return "sstSymbols";
case 0x0127: return "sstSrcModule";
case 0x0128: return "sstLibraries";
case 0x0129: return "sstGlobalSym";
case 0x012A: return "sstGlobalPub";
case 0x012B: return "sstGlobalTypes";
case 0x012D: return "sstSegMap";
case 0x012E: return "sstSegName";
case 0x0133: return "sstFileIndex";
case 0x0134: return "sstStaticSym";
default: return "Unknown";
}
}
void print_properties(RSMProperties *props) {
printf("RSM File Properties:\n");
printf("Signature: %.4s\n", props->signature);
printf("Directory Offset: %u\n", props->lfoDir);
printf("Directory Header:\n");
printf(" - cbDirHeader: %u\n", props->cbDirHeader);
printf(" - cbDirEntry: %u\n", props->cbDirEntry);
printf(" - cDir: %u\n", props->cDir);
printf(" - lfoNextDir: %u\n", props->lfoNextDir);
printf(" - flags: %u\n", props->flags);
printf("Subsections:\n");
for (int i = 0; i < props->cDir; i++) {
printf(" - Type: %s (0x%04X), Module: %u, Offset: %u, Size: %u\n",
get_subsection_name(props->subsections[i].type),
props->subsections[i].type,
props->subsections[i].iMod,
props->subsections[i].lfo,
props->subsections[i].cb);
}
}
void read_rsm(const char *filename, RSMProperties *props) {
FILE *f = fopen(filename, "rb");
if (!f) {
perror("File open error");
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(size);
fread(data, 1, size, f);
fclose(f);
int pos = 0;
memcpy(props->signature, data + pos, 4);
pos += 4;
props->lfoDir = *(uint32_t *)(data + pos);
pos = props->lfoDir;
props->cbDirHeader = *(uint16_t *)(data + pos);
pos += 2;
props->cbDirEntry = *(uint16_t *)(data + pos);
pos += 2;
props->cDir = *(uint16_t *)(data + pos);
pos += 2;
props->lfoNextDir = *(uint32_t *)(data + pos);
pos += 4;
props->flags = *(uint32_t *)(data + pos);
pos += 4;
for (int i = 0; i < props->cDir; i++) {
props->subsections[i].type = *(uint16_t *)(data + pos);
pos += 2;
props->subsections[i].iMod = *(uint16_t *)(data + pos);
pos += 2;
props->subsections[i].lfo = *(uint32_t *)(data + pos);
pos += 4;
props->subsections[i].cb = *(uint32_t *)(data + pos);
pos += 4;
}
free(data);
print_properties(props);
}
void write_skeleton(const char *output_filename) {
FILE *f = fopen(output_filename, "wb");
if (!f) {
perror("File write error");
return;
}
// Skeleton: NB05, lfoDir=20, dummy header and entry
fwrite("NB05", 1, 4, f);
fwrite("\x00\x00\x00\x14", 1, 4, f); // lfoDir = 20
fwrite("\x14\x00\x0C\x00\x01\x00\x00\x00\x00\x00", 1, 10, f); // Header
fwrite("\x20\x01\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00", 1, 12, f); // Dummy sstModule entry
fclose(f);
printf("Wrote skeleton .RSM to %s\n", output_filename);
}
int main() {
RSMProperties props = {0};
read_rsm("example.rsm", &props);
write_skeleton("skeleton.rsm");
return 0;
}