├── src ├── global.d.ts ├── main.ts ├── memlog │ ├── index.ts │ ├── config.ts │ ├── utils.ts │ ├── geometry.ts │ ├── region.test.ts │ ├── memlog.ts │ ├── region.ts │ ├── layer.test.ts │ ├── log.ts │ └── layer.ts ├── app │ ├── Legend.svelte │ ├── Uploader.svelte │ ├── Zoomer.svelte │ ├── Transform.svelte │ ├── Footer.svelte │ ├── AddressColumn.svelte │ ├── Region.svelte │ ├── App.svelte │ └── MemoryView.svelte ├── health.ts ├── configs │ └── webkit.config.ts ├── worker.ts └── sample.ts ├── public ├── favicon.png ├── assets │ ├── loading.gif │ ├── stop.svg │ ├── play.svg │ ├── zoom-in.svg │ └── zoom-out.svg ├── index.html └── global.css ├── .gitignore ├── jest.config.js ├── tsconfig.json ├── README.md ├── rollup.config.worker.js ├── package.json ├── scripts └── cli.ts └── rollup.config.js /src/global.d.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/memlog/master/public/favicon.png -------------------------------------------------------------------------------- /public/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/basuke/memlog/master/public/assets/loading.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | .vscode/ 6 | .pnpm-debug.log 7 | pnpm-lock.yaml 8 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './app/App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.body, 5 | props: { 6 | } 7 | }); 8 | 9 | export default app; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | 4 | "include": ["src/**/*", "scripts/**/*"], 5 | "exclude": ["node_modules/*", "__sapper__/*", "public/*"] 6 | } -------------------------------------------------------------------------------- /public/assets/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/assets/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memlog viewer 2 | 3 | This is the web application to visualize `memlog` file. 4 | 5 | ## Get started 6 | 7 | Install the dependencies... 8 | 9 | ```bash 10 | cd memlog 11 | npm install 12 | ``` 13 | 14 | ...then start [Rollup](https://rollupjs.org): 15 | 16 | ```bash 17 | npm run dev 18 | ``` 19 | -------------------------------------------------------------------------------- /src/memlog/index.ts: -------------------------------------------------------------------------------- 1 | export { Regions, Memlog } from './memlog'; 2 | export type { Region } from './region'; 3 | export { parse, Log } from './log'; 4 | export { bytes, hex, K, M } from "./utils"; 5 | export type { Config, TypeConfig } from './config'; 6 | export { Geometry } from './geometry'; 7 | export type { Row } from './geometry'; 8 | -------------------------------------------------------------------------------- /public/assets/zoom-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/zoom-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/memlog/config.ts: -------------------------------------------------------------------------------- 1 | export type TypeConfig = { 2 | color?: string; 3 | border?: boolean|number|string|{width?:number, color?:string}; 4 | }; 5 | 6 | export type LayerConfig = { 7 | types: Record; 8 | disabled?: boolean; 9 | management?: "managed"|"flexible"; 10 | }; 11 | 12 | export type Config = { 13 | layers: Record; 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte app 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/app/Legend.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | {#each Object.keys(types) as type} 9 | {type} 10 | {/each} 11 |
12 | 13 | 27 | -------------------------------------------------------------------------------- /src/app/Uploader.svelte: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/Zoomer.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | {#if left} 12 | 1 row = {bytes(rowBytes)} 13 | {/if} 14 | 15 | 16 | {#if !left} 17 | 1 row = {bytes(rowBytes)} 18 | {/if} 19 |
20 | 21 | -------------------------------------------------------------------------------- /src/memlog/utils.ts: -------------------------------------------------------------------------------- 1 | export const K = 1024; 2 | export const M = 1024 * K; 3 | export const G = 1024 * M; 4 | 5 | export function roundDown(val, alignment) { 6 | return Math.floor(val / alignment) * alignment; 7 | } 8 | 9 | export function roundUp(val, alignment) { 10 | return Math.ceil(val / alignment) * alignment; 11 | } 12 | 13 | export function hex(val, prefix = '0x') { 14 | return prefix + val.toString(16); 15 | } 16 | 17 | export function arrayOfSize(count: number) { 18 | return Array.from(new Array(count)).map((_, index) => index); 19 | } 20 | 21 | export function bytes(value: number): string { 22 | let unit = 'bytes'; 23 | const units = ['KB', 'MB', 'GB']; 24 | 25 | while (value >= 1024 && units.length > 0) { 26 | unit = units.shift(); 27 | const mod = value % 1024; 28 | value = Math.floor(value / 1024); 29 | } 30 | return value + unit; 31 | } -------------------------------------------------------------------------------- /src/app/Transform.svelte: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /rollup.config.worker.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import typescript from '@rollup/plugin-typescript'; 5 | 6 | const production = !process.env.ROLLUP_WATCH; 7 | 8 | export default { 9 | input: 'src/worker.ts', 10 | output: { 11 | sourcemap: true, 12 | format: 'iife', 13 | name: 'app', 14 | file: 'public/build/worker.js' 15 | }, 16 | plugins: [ 17 | // If you have external dependencies installed from 18 | // npm, you'll most likely need these plugins. In 19 | // some cases you'll need additional configuration - 20 | // consult the documentation for details: 21 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 22 | resolve({ 23 | browser: true, 24 | }), 25 | commonjs(), 26 | typescript({ 27 | sourceMap: !production, 28 | inlineSources: !production 29 | }), 30 | 31 | // If we're building for production (npm run build 32 | // instead of npm run dev), minify 33 | production && terser() 34 | ], 35 | watch: { 36 | clearScreen: false 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /public/global.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | color: #333; 9 | margin: 0; 10 | padding: 0; 11 | box-sizing: border-box; 12 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; 13 | } 14 | 15 | a { 16 | color: rgb(0,100,200); 17 | text-decoration: none; 18 | } 19 | 20 | a:hover { 21 | text-decoration: underline; 22 | } 23 | 24 | a:visited { 25 | color: rgb(0,80,160); 26 | } 27 | 28 | label { 29 | display: block; 30 | } 31 | 32 | input, button, select, textarea { 33 | font-family: inherit; 34 | font-size: inherit; 35 | -webkit-padding: 0.4em 0; 36 | padding: 0.4em; 37 | margin: 0 0 0.5em 0; 38 | box-sizing: border-box; 39 | border: 1px solid #ccc; 40 | border-radius: 2px; 41 | } 42 | 43 | input:disabled { 44 | color: #ccc; 45 | } 46 | 47 | button { 48 | color: #333; 49 | background-color: #f4f4f4; 50 | outline: none; 51 | } 52 | 53 | button:disabled { 54 | color: #999; 55 | } 56 | 57 | button:not(:disabled):active { 58 | background-color: #ddd; 59 | } 60 | 61 | button:focus { 62 | border-color: #666; 63 | } 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memlog", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "cli": "ts-node ./scripts/cli.ts", 7 | "build": "rollup -c", 8 | "worker": "rollup -c rollup.config.worker.js", 9 | "dev": "rollup -c -w", 10 | "start": "sirv public --no-clear", 11 | "check": "svelte-check --tsconfig ./tsconfig.json" 12 | }, 13 | "devDependencies": { 14 | "@rollup/plugin-commonjs": "^17.1.0", 15 | "@rollup/plugin-node-resolve": "^11.0.0", 16 | "@rollup/plugin-typescript": "^8.0.0", 17 | "@tsconfig/svelte": "^2.0.0", 18 | "@types/jest": "^27.0.2", 19 | "jest": "^27.3.1", 20 | "rollup": "^2.3.4", 21 | "rollup-plugin-css-only": "^3.1.0", 22 | "rollup-plugin-livereload": "^2.0.0", 23 | "rollup-plugin-svelte": "^7.0.0", 24 | "rollup-plugin-terser": "^7.0.0", 25 | "svelte": "^3.0.0", 26 | "svelte-check": "^2.0.0", 27 | "svelte-preprocess": "^4.0.0", 28 | "ts-jest": "^27.0.7", 29 | "tslib": "^2.0.0", 30 | "typescript": "^4.0.0" 31 | }, 32 | "dependencies": { 33 | "cli-progress": "^3.9.1", 34 | "minimist": "^1.2.5", 35 | "sirv-cli": "^1.0.0", 36 | "ts-node": "^10.4.0" 37 | } 38 | } -------------------------------------------------------------------------------- /src/health.ts: -------------------------------------------------------------------------------- 1 | import { Log, Regions, parse } from './memlog'; 2 | import { LayerFactory } from './memlog/layer'; 3 | import config from './configs/webkit.config'; 4 | 5 | const logPath = 'C:\\Users\\ysuzuki\\Downloads\\drive-download-20211104T225030Z-001\\memlog-62-11-03-23-57-32.log'; 6 | export let logs = parse(require('fs').readFileSync(logPath).toString()).filter(log => log.layer === 'bmalloc'); 7 | export let regions = new Regions(config, new LayerFactory(config.layers)); 8 | 9 | export let healthCheck = () => { 10 | if (!regions.layers?.bmalloc) return; 11 | 12 | let lastRegion; 13 | for (const region of regions.layers.bmalloc.regions) { 14 | if (region.addr >= region.end) { 15 | throw new Error(`invalid region: addr 0x${region.addr}`); 16 | } 17 | if (lastRegion) { 18 | if (lastRegion.addr >= region.addr || lastRegion.end > region.addr) { 19 | throw new Error(`invalid regions: addr 0x${region.addr}`); 20 | } 21 | } 22 | lastRegion = region; 23 | } 24 | }; 25 | 26 | export let step = () => { 27 | if (logs.length === 0) return null; 28 | regions = regions.process(logs[0]); 29 | healthCheck(); 30 | logs.shift(); 31 | return logs.length ? logs[0].line : null; 32 | }; 33 | 34 | export let stepUntil = (condition: (log: Log) => boolean) => { 35 | while (logs.length) { 36 | step(); 37 | if (logs.length && condition(logs[0])) return logs[0].line; 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /scripts/cli.ts: -------------------------------------------------------------------------------- 1 | import { hex, parse } from '../src/memlog'; 2 | import { Actions } from '../src/memlog/log'; 3 | 4 | const fs = require('fs'); 5 | 6 | const argv = require('minimist')(process.argv.slice(2)); 7 | 8 | function parseLogFile(path) { 9 | const source = fs.readFileSync(path).toString().substr(0, 10000); 10 | const logs = parse(source); 11 | return logs; 12 | } 13 | 14 | const path = argv._[0]; 15 | console.log(`parsing log file: ${path}`); 16 | const logs = parseLogFile(path); 17 | 18 | for (let i = 0; i < logs.length; i++) { 19 | const log = logs[i]; 20 | const nextLog = logs[i + 1]; 21 | 22 | if (log.action === Actions.Begin || log.action === Actions.End) continue; 23 | 24 | if (log.layer === 'vm') { 25 | log.layer = 'mmap'; 26 | if (log.action === Actions.Split) { 27 | if (nextLog.action === Actions.Free) { 28 | i++; 29 | log.action = Actions.Free; 30 | log.addr = nextLog.addr; 31 | log.line += "\n" + nextLog.line; 32 | } 33 | } else if (log.action === Actions.Alloc) { 34 | log.type = 'anon'; 35 | } 36 | } else if (log.layer === 'page') { 37 | log.layer = 'mmap'; 38 | if (log.action == Actions.Free) { 39 | log.action = Actions.Alloc; 40 | log.type = 'free'; 41 | } 42 | } 43 | console.log(`${log.layer} ${log.action} 0x${hex(log.addr)} ${log.size ?? ''}`); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/app/Footer.svelte: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 | {#if regions && regions.log}
{regions.log.line}
{/if} 31 |
32 | {#if playing} 33 | 34 | {:else} 35 | 36 | {/if} 37 | 38 |
39 | 40 | {#each Object.keys(config.layers) as name} 41 | 42 | {/each} 43 |
{name}
44 |
45 | 46 | -------------------------------------------------------------------------------- /src/app/AddressColumn.svelte: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 51 | {#each addressLabels(geo, start, end) as label(label.y)} 52 | {label.text} 53 | {/each} 54 | 55 | -------------------------------------------------------------------------------- /src/configs/webkit.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "../memlog"; 2 | 3 | const config: Config = { 4 | layers: { 5 | bmalloc: { 6 | types: { 7 | free: { color: 'WhiteSmoke' }, 8 | large: { color: 'MediumSlateBlue' }, 9 | chunk: { color: 'Wheat' }, 10 | perProcess: { color: 'MediumSeaGreen' }, 11 | perThread: { color: 'MediumVioletRed' }, 12 | vector: { color: 'Turquoise' }, 13 | objectTypeTable: { color: 'DarkOliveGreen' }, 14 | }, 15 | }, 16 | chunk: { 17 | management: "flexible", 18 | disabled: false, 19 | types: { 20 | header: { color: 'RosyBrown' }, 21 | page: { color: 'SandyBrown' }, 22 | decommit: { color: 'Wheat' }, 23 | }, 24 | }, 25 | mmap: { 26 | management: "flexible", 27 | types: { 28 | anon: { color: 'rgba(0,0,0,0)', border: 0.5 }, 29 | free: { color: 'rgba(0,192,0,0.3)', border: 0.5 }, 30 | no: { color: 'rgba(255,0,0,0.6)' }, 31 | }, 32 | }, 33 | // jsc: { 34 | // types: { 35 | // fastMalloc: { color: 'MediumVioletRed', border: true }, 36 | // "fastMalloc-aligned": { color: 'RebeccaPurple', border: true }, 37 | // "iso-aligned": { color: 'Orchid', border: true }, 38 | // "iso-decommit": { color: 'MistyRose', border: true }, 39 | // }, 40 | // }, 41 | }, 42 | }; 43 | 44 | export default config; 45 | -------------------------------------------------------------------------------- /src/worker.ts: -------------------------------------------------------------------------------- 1 | import { Log, parse } from './memlog'; 2 | import config from './configs/webkit.config'; 3 | import type { LayerConfig } from './memlog/config'; 4 | import { Actions } from './memlog/log'; 5 | 6 | console.log('Worker started.'); 7 | 8 | function layerConfigOf(log): LayerConfig|undefined { 9 | return config.layers[log.layer]; 10 | } 11 | 12 | function mergeLogs(logs) { 13 | const result = []; 14 | let lastLog; 15 | for (const log of logs) { 16 | const merged = merge(lastLog, log); 17 | if (merged) { 18 | lastLog = merged; 19 | } else { 20 | if (lastLog) { 21 | result.push(lastLog); 22 | } 23 | lastLog = log; 24 | } 25 | } 26 | if (lastLog) { 27 | result.push(lastLog); 28 | } 29 | return result; 30 | } 31 | 32 | function merge(a: Log, b: Log): Log|undefined { 33 | if (!a || !b) return undefined; 34 | if (a.action != b.action || a.layer != b.layer) return undefined; 35 | if (a.action !== Actions.Alloc && a.action !== Actions.Free) return undefined; 36 | 37 | const result = a.clone(); 38 | result.line += "\n" + b.line; 39 | 40 | if ((a.addr + a.size) === b.addr || (b.addr + b.size) === a.addr) { 41 | result.addr = Math.min(a.addr, b.addr); 42 | result.size = a.size + b.size; 43 | return result; 44 | } else { 45 | return undefined; 46 | } 47 | } 48 | 49 | onmessage = function(e) { 50 | console.log('Worker received message'); 51 | const source = e.data; 52 | let logs = parse(source).filter(log => { 53 | const layerConfig = layerConfigOf(log); 54 | return (layerConfig && !layerConfig.disabled); 55 | }).slice(0, 10000); 56 | 57 | // logs = mergeLogs(logs); 58 | 59 | postMessage(logs.length); 60 | for (const log of logs) { 61 | postMessage(log.serialize()); 62 | } 63 | postMessage(null); 64 | }; 65 | -------------------------------------------------------------------------------- /src/app/Region.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/sample.ts: -------------------------------------------------------------------------------- 1 | import { Region, M } from "./memlog"; 2 | 3 | export const source1 = `# map file format 4 | # vm 5 | 6 | alloc ts:122 layer:vm addr:0x20014000 size:0x20700000+1049806-0x20014000 type:rw 7 | 8 | # allocation: start and size are mandatory. others are optional 9 | alloc ts:12345 addr:0x20014000 size:5242880 type:free layer:bmalloc 10 | alloc ts:12345 addr:0x20514000 size:1015808 type:chunk layer:bmalloc 11 | alloc ts:12345 addr:0x20614000 size:970752 type:free layer:bmalloc 12 | alloc ts:12345 addr:0x20700000 size:1049806 type:large layer:bmalloc 13 | 14 | # free: 15 | free ts:12345 addr:0x20614000 layer:bmalloc 16 | 17 | # split 18 | split ts:12345 addr:0x20014000 size:1015808 layer:bmalloc 19 | 20 | # mod 21 | mod ts:12345 addr:0x20014000 layer:bmalloc type:chunk 22 | `; 23 | 24 | export const source2 = ` 25 | # memlog started. 26 | alloc ts:8 layer:vm addr:0x200f80000 size:49152 type:rw 27 | alloc ts:8 layer:vm addr:0x200f8c000 size:16384 type:rw 28 | alloc ts:8 layer:vm addr:0x200f94000 size:2097152 type:rw 29 | split ts:8 layer:vm addr:0x200f94000 size:442368 30 | free ts:8 layer:vm addr:0x200f94000 31 | split ts:8 layer:vm addr:0x201000000 size:1048576 32 | free ts:8 layer:vm addr:0x201100000 33 | alloc ts:8 layer:vm addr:0x200f94000 size:16384 type:rw 34 | alloc ts:8 layer:bmalloc addr:0x201000000 size:1048576 type:free 35 | mod ts:8 layer:bmalloc addr:0x201000000 type:large 36 | alloc ts:8 layer:vm addr:0x200f98000 size:16384 type:rw 37 | alloc ts:8 layer:vm addr:0x200f9c000 size:16384 type:rw 38 | mod ts:8 layer:bmalloc addr:0x201000000 type:chunk 39 | alloc ts:11 layer:vm addr:0x201100000 size:2097152 type:rw 40 | split ts:11 layer:vm addr:0x201100000 size:1048576 41 | free ts:11 layer:vm addr:0x201200000 42 | `; 43 | 44 | export const samples: Region[] = [ 45 | { type: 'green', addr: 0x20014000, end: 0x20014000 + 5 * M}, 46 | { type: 'gray', addr: 0x20514000, end: 0x20514000 + 1 * M - 0x8000}, 47 | { type: 'pink', addr: 0x20614000, end: 0x20614000 + M - 0x13000}, 48 | { type: 'orange', addr: 0x20700000, end: 0x20700000 + 0x100000 + 1230}, 49 | ]; 50 | -------------------------------------------------------------------------------- /src/memlog/geometry.ts: -------------------------------------------------------------------------------- 1 | import { roundDown, roundUp } from "./utils"; 2 | 3 | export type Row = { 4 | row: number; 5 | start: number; 6 | end: number; 7 | }; 8 | 9 | export type Rect = { 10 | top: number; 11 | right: number; 12 | bottom: number; 13 | left: number; 14 | }; 15 | 16 | export class Geometry { 17 | rowBytes: number; 18 | rowWidth: number; 19 | rowHeight: number; 20 | 21 | constructor(rowBytes: number, rowWidth: number, rowHeight: number) { 22 | this.rowBytes = rowBytes; 23 | this.rowWidth = rowWidth; 24 | this.rowHeight = rowHeight; 25 | } 26 | 27 | makeRows(start: number, end: number): Row[] { 28 | const roundedStart = this.floor(start); 29 | const roundedEnd = this.ceil(end); 30 | const count = this.addressToRow(roundedEnd - roundedStart); 31 | const startRow = this.addressToRow(start); 32 | const rows = []; 33 | 34 | for (let n = 0; n < count; ++n) { 35 | let s = 0; 36 | let e = this.rowBytes; 37 | 38 | if (count === 1 || n === 0) { 39 | s = (start - roundedStart); 40 | } 41 | 42 | if (count === 1 || n === count - 1) { 43 | e = (end - this.floor(end - 1)); 44 | } 45 | rows.push({row: startRow + n, start: s, end: e}) 46 | }; 47 | return rows; 48 | } 49 | 50 | rowToRect(row: Row, inset: number = 0): Rect { 51 | const top = this.rowToHeight(row.row) + inset; 52 | const bottom = top + this.rowHeight - inset; 53 | const left = this.rowWidth * row.start / this.rowBytes + inset; 54 | const right = this.rowWidth * row.end / this.rowBytes - inset; 55 | return {top, right, bottom, left}; 56 | } 57 | 58 | addressToRow(val: number): number { 59 | return Math.floor(val / this.rowBytes); 60 | } 61 | 62 | rowToHeight(val: number): number { 63 | return val * this.rowHeight; 64 | } 65 | 66 | floor(val: number): number { 67 | return roundDown(val, this.rowBytes); 68 | } 69 | 70 | ceil(val: number): number { 71 | return roundUp(val, this.rowBytes); 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /src/memlog/region.test.ts: -------------------------------------------------------------------------------- 1 | import { intersect, Region, subtract } from "./region"; 2 | 3 | const a: Region = { addr: 10, end: 20, type: 'foo' }; 4 | 5 | describe("test intersect of region", () => { 6 | test("a is contained by b", () => { 7 | expect(intersect(a, {addr:5, end:25})).toMatchObject(a); 8 | }) 9 | 10 | test("b is contained by a", () => { 11 | expect(intersect(a, { addr:15, end:18})).toMatchObject({addr:15, end:18, type:'foo'}); 12 | }) 13 | 14 | test("a and b are overwrapped at the a'beginning", () => { 15 | expect(intersect(a, { addr:8, end:12})).toMatchObject({addr:10, end:12, type:'foo'}); 16 | }) 17 | 18 | test("a and b are overwrapped at the a'end", () => { 19 | expect(intersect(a, { addr:15, end:25})).toMatchObject({addr:15, end:20, type:'foo'}); 20 | }) 21 | 22 | test("a and b is not overwrapped entierly", () => { 23 | expect(intersect(a, {addr:30, end:35})).toBeNull(); 24 | }) 25 | 26 | test("a nad b are attached at a.end", () => { 27 | expect(intersect(a, { addr:20, end:25})).toBeNull(); 28 | }) 29 | 30 | test("a and b are attached at a.addr", () => { 31 | expect(intersect(a, { addr:3, end:10})).toBeNull(); 32 | }) 33 | }); 34 | 35 | describe("test subtract of regions", () => { 36 | test("a is contained by b", () => { 37 | expect(subtract(a, {addr:5, end:25})).toMatchObject([ 38 | ]); 39 | }) 40 | 41 | test("b is contained by a", () => { 42 | expect(subtract(a, { addr:15, end:18})).toMatchObject([ 43 | {addr:10, end:15, type:'foo'}, 44 | {addr:18, end:20, type:'foo'}, 45 | ]); 46 | }) 47 | 48 | test("a and b are overwrapped at the a'beginning", () => { 49 | expect(subtract(a, { addr:8, end:12})).toMatchObject([ 50 | {addr:12, end:20, type:'foo'} 51 | ]); 52 | }) 53 | 54 | test("a and b are overwrapped at the a'end", () => { 55 | expect(subtract(a, { addr:15, end:25})).toMatchObject([ 56 | {addr:10, end:15, type:'foo'} 57 | ]); 58 | }) 59 | 60 | test("a and b is not overwrapped entierly", () => { 61 | expect(subtract(a, {addr:30, end:35})).toMatchObject([ 62 | a, 63 | ]); 64 | }) 65 | 66 | test("a nad b are attached at a.end", () => { 67 | expect(subtract(a, { addr:20, end:25})).toMatchObject([ 68 | a, 69 | ]); 70 | }) 71 | 72 | test("a and b are attached at a.addr", () => { 73 | expect(subtract(a, { addr:3, end:10})).toMatchObject([ 74 | a, 75 | ]); 76 | }) 77 | 78 | test("a contains b and they are attached at the a'beginning", () => { 79 | expect(subtract(a, { addr:10, end:12})).toMatchObject([ 80 | {addr:12, end:20, type:'foo'} 81 | ]); 82 | }) 83 | 84 | test("a contains b and they are attached at the a'end", () => { 85 | expect(subtract(a, { addr:15, end:20})).toMatchObject([ 86 | {addr:10, end:15, type:'foo'} 87 | ]); 88 | }) 89 | }); 90 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | import sveltePreprocess from 'svelte-preprocess'; 7 | import typescript from '@rollup/plugin-typescript'; 8 | import css from 'rollup-plugin-css-only'; 9 | 10 | const production = !process.env.ROLLUP_WATCH; 11 | 12 | function serve() { 13 | let server; 14 | 15 | function toExit() { 16 | if (server) server.kill(0); 17 | } 18 | 19 | return { 20 | writeBundle() { 21 | if (server) return; 22 | server = require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 23 | stdio: ['ignore', 'inherit', 'inherit'], 24 | shell: true 25 | }); 26 | 27 | process.on('SIGTERM', toExit); 28 | process.on('exit', toExit); 29 | } 30 | }; 31 | } 32 | 33 | function buildWorker(dev) { 34 | let proc; 35 | 36 | function toExit() { 37 | if (proc) proc.kill(0); 38 | } 39 | 40 | return { 41 | writeBundle() { 42 | if (proc) return; 43 | const args = ['run', 'worker', '--']; 44 | if (dev) args.push('--watch'); 45 | proc = require('child_process').spawn('npm', args, { 46 | stdio: ['ignore', 'inherit', 'inherit'], 47 | shell: true 48 | }); 49 | 50 | process.on('SIGTERM', toExit); 51 | process.on('exit', toExit); 52 | } 53 | }; 54 | } 55 | 56 | export default { 57 | input: 'src/main.ts', 58 | output: { 59 | sourcemap: true, 60 | format: 'iife', 61 | name: 'app', 62 | file: 'public/build/bundle.js' 63 | }, 64 | plugins: [ 65 | svelte({ 66 | preprocess: sveltePreprocess({ sourceMap: !production }), 67 | compilerOptions: { 68 | // enable run-time checks when not in production 69 | dev: !production 70 | } 71 | }), 72 | // we'll extract any component CSS out into 73 | // a separate file - better for performance 74 | css({ output: 'bundle.css' }), 75 | 76 | // If you have external dependencies installed from 77 | // npm, you'll most likely need these plugins. In 78 | // some cases you'll need additional configuration - 79 | // consult the documentation for details: 80 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 81 | resolve({ 82 | browser: true, 83 | dedupe: ['svelte'] 84 | }), 85 | commonjs(), 86 | typescript({ 87 | sourceMap: !production, 88 | inlineSources: !production 89 | }), 90 | 91 | buildWorker(!production), 92 | 93 | // In dev mode, call `npm run start` once 94 | // the bundle has been generated 95 | !production && serve(), 96 | 97 | // Watch the `public` directory and refresh the 98 | // browser on changes when not in production 99 | !production && livereload('public'), 100 | 101 | // If we're building for production (npm run build 102 | // instead of npm run dev), minify 103 | production && terser() 104 | ], 105 | watch: { 106 | clearScreen: false 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /src/memlog/memlog.ts: -------------------------------------------------------------------------------- 1 | import { Actions, Log } from "./log"; 2 | import { Layer, LayerFactory } from './layer'; 3 | import { updateStartEnd } from "./region"; 4 | import type { Config } from "./config"; 5 | 6 | export class Regions { 7 | layers: Record = {}; 8 | addr: number = undefined; 9 | end: number = undefined; 10 | log: Log; 11 | config: Config; 12 | factory: LayerFactory; 13 | 14 | constructor(config: Config, factory: LayerFactory) { 15 | this.config = config; 16 | this.factory = factory; 17 | } 18 | 19 | clone(): Regions { 20 | const regions = new Regions(this.config, this.factory); 21 | regions.layers = {...this.layers}; 22 | regions.addr = this.addr; 23 | regions.end = this.end; 24 | return regions; 25 | } 26 | 27 | activeLayers(): Layer[] { 28 | return Object.keys(this.config.layers).map(name => this.layers[name]).filter(layer => layer); 29 | } 30 | 31 | process(log: Log): Regions { 32 | const layerName = log.layer; 33 | let layer = (layerName in this.layers) ? this.layers[layerName] : this.factory.create(layerName); 34 | if (!layer) return this; 35 | 36 | switch (log.action) { 37 | case Actions.Alloc: 38 | layer = layer.alloc(log.addr, log.size, log.type); 39 | break; 40 | 41 | case Actions.Free: 42 | layer = layer.free(log.addr, log.size); 43 | break; 44 | 45 | case Actions.Split: 46 | layer = layer.split(log.addr, log.size); 47 | break; 48 | 49 | case Actions.Merge: 50 | layer = layer.merge(log.addr, log.other); 51 | break; 52 | 53 | case Actions.Mod: 54 | layer = layer.mod(log.addr, log.type); 55 | break; 56 | 57 | default: { 58 | layer = layer.clone(); 59 | break; 60 | } 61 | } 62 | 63 | const regions = this.clone(); 64 | regions.layers[layerName] = layer; 65 | if (layer.addr !== undefined) { 66 | updateStartEnd(regions, layer); 67 | } 68 | regions.log = log; 69 | return regions; 70 | } 71 | } 72 | 73 | export class Memlog { 74 | history: Regions[] = []; 75 | addr: number; 76 | end: number; 77 | config: Config; 78 | factory: LayerFactory; 79 | limit: number = 10000; 80 | 81 | constructor(config: Config) { 82 | this.config = config; 83 | this.factory = new LayerFactory(config.layers); 84 | } 85 | 86 | get length() { 87 | return this.history.length; 88 | } 89 | 90 | get latest(): Regions { 91 | return this.getRegions(this.length - 1); 92 | } 93 | 94 | getRegions(index): Regions { 95 | if (index < 0 || index >= this.length) return new Regions(this.config, this.factory); 96 | return this.history[index]; 97 | } 98 | 99 | process(log: Log) { 100 | const prevRegions = this.latest; 101 | try { 102 | const regions = prevRegions.process(log); 103 | updateStartEnd(this, regions); 104 | if (this.history.length > this.limit) { 105 | this.history.splice(0, this.history.length - this.limit + 1); 106 | } 107 | this.history.push(regions); 108 | } catch (e) { 109 | console.error(e); 110 | console.log(log.line); 111 | } 112 | } 113 | }; 114 | -------------------------------------------------------------------------------- /src/memlog/region.ts: -------------------------------------------------------------------------------- 1 | export type Region = { 2 | addr: number; 3 | end: number; 4 | type?: string; 5 | // logs?: string[]; 6 | }; 7 | 8 | export function overwrap(a: Region, b: Region): boolean { 9 | return !(a.end <= b.addr || b.end <= a.addr); 10 | } 11 | 12 | export function contains(r: Region, addr: number): boolean { 13 | return addr >= r.addr && addr < r.end; 14 | } 15 | 16 | export function intersect(a: Region, b: Region): Region|null { 17 | if (a.end <= b.addr || b.end <= a.addr) return null; 18 | const addr = Math.max(a.addr, b.addr); 19 | const end = Math.min(a.end, b.end); 20 | return { addr, end, type: a.type }; 21 | } 22 | 23 | export function subtract(a: Region, b: Region): Region[] { 24 | const i = intersect(a, b); 25 | if (!i) return [{...a}]; 26 | 27 | const type = a.type; 28 | return [ 29 | { addr: a.addr, end: i.addr, type }, 30 | { addr: i.end, end: a.end, type }, 31 | ].filter(r => r.addr < r.end); 32 | } 33 | 34 | export interface StartEnd { 35 | addr: number; 36 | end: number; 37 | }; 38 | 39 | export function updateStartEnd(a: StartEnd, b: StartEnd) { 40 | if (a.addr === undefined) { 41 | a.addr = b.addr; 42 | a.end = b.end; 43 | } else { 44 | a.addr = Math.min(a.addr, b.addr); 45 | a.end = Math.max(a.end, b.end); 46 | } 47 | } 48 | 49 | export class SortedRegions { 50 | regions: Region[] = []; 51 | addr: number = undefined; 52 | end: number = undefined; 53 | 54 | get length(): number { 55 | return this.regions.length; 56 | } 57 | 58 | get empty(): boolean { 59 | return this.length === 0; 60 | } 61 | 62 | regionWith(addr: number): Region|null { 63 | const pos = this.indexOf(addr); 64 | return pos >= 0 ? this.regionAt(pos) : null; 65 | } 66 | 67 | regionAt(index: number): Region { 68 | return this.regions[index]; 69 | } 70 | 71 | copyRegionAt(index: number): Region { 72 | const region = this.regions[index]; 73 | return { ...region }; 74 | } 75 | 76 | insertPosition(addr: number): number { 77 | const regions = this.regions; 78 | const indexOf = (from: number, end: number): number => { 79 | if (from === end) return from; 80 | 81 | const pos = from + Math.floor((end - from) / 2); 82 | const region = regions[pos]; 83 | const delta = addr - region.addr; 84 | if (!delta) return pos; 85 | if (delta < 0) return indexOf(from, pos); 86 | return indexOf(pos + 1, end); 87 | }; 88 | return indexOf(0, regions.length); 89 | } 90 | 91 | indexOf(addr: number): number { 92 | const pos = this.insertPosition(addr); 93 | if (pos >= this.length) return -1; 94 | 95 | const region = this.regions[pos]; 96 | if (region.addr !== addr) return -1; 97 | return pos; 98 | } 99 | 100 | find(addr: number): [number, Region?] { 101 | let pos = this.insertPosition(addr); 102 | let region = this.regionAt(pos); 103 | 104 | if (region) { 105 | if (region.end > addr) { 106 | return [pos, region]; 107 | } 108 | } else if (pos > 0) { 109 | pos -= 1; 110 | region = this.regionAt(pos); 111 | if (region.end > addr) { 112 | return [pos, region]; 113 | } 114 | } 115 | return [-1, null]; 116 | } 117 | 118 | insert(pos: number, region: Region) { 119 | this.regions.splice(pos, 0, region); 120 | updateStartEnd(this, region); 121 | } 122 | 123 | remove(pos: number) { 124 | this.regions.splice(pos, 1); 125 | } 126 | 127 | removeAndInsert(start: number, stop: number, regions: Region[]) { 128 | this.regions.splice(start, stop - start, ...regions); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/memlog/layer.test.ts: -------------------------------------------------------------------------------- 1 | import type { LayerConfig } from "./config"; 2 | import { FlexibleLayer } from "./layer"; 3 | 4 | const config: LayerConfig = { 5 | types: { }, 6 | }; 7 | 8 | describe("tests FlexibleLayer", () => { 9 | const layer = new FlexibleLayer('vm', config); 10 | layer.regions = [ 11 | { addr: 10, end: 20, type: 'a'}, 12 | { addr: 30, end: 40, type: 'a'}, 13 | ]; 14 | 15 | test("simple alloc without overwrap in the middle", () => { 16 | const result = layer.alloc(22, 5, 'b'); 17 | expect(layer.length).toEqual(2); 18 | expect(result.length).toEqual(3); 19 | expect(result.regions[1]).toEqual({ addr: 22, end: 27, type: 'b' }); 20 | }); 21 | 22 | test("simple alloc without overwrap at the beginning", () => { 23 | const result = layer.alloc(2, 5, 'b'); 24 | expect(result.length).toEqual(3); 25 | expect(result.regions[0]).toEqual({ addr: 2, end: 7, type: 'b' }); 26 | }); 27 | 28 | test("simple alloc without overwrap at the end", () => { 29 | const result = layer.alloc(45, 5, 'b'); 30 | expect(result.length).toEqual(3); 31 | expect(result.regions[2]).toEqual({ addr: 45, end: 50, type: 'b' }); 32 | }); 33 | 34 | // === 35 | 36 | test("alloc between 0 and 1, attached with 0", () => { 37 | const result = layer.alloc(20, 5, 'a'); 38 | expect(result.length).toEqual(2); 39 | expect(result.regions[0]).toEqual({ addr: 10, end: 25, type: 'a' }); 40 | }); 41 | 42 | test("alloc between 0 and 1, overwrapped with 0", () => { 43 | const result = layer.alloc(15, 10, 'a'); 44 | expect(result.length).toEqual(2); 45 | expect(result.regions[0]).toEqual({ addr: 10, end: 25, type: 'a' }); 46 | }); 47 | 48 | test("alloc between 0 and 1, overwrapped with 0 but different type", () => { 49 | const result = layer.alloc(15, 10, 'b'); 50 | expect(result.length).toEqual(3); 51 | expect(result.regions[0]).toEqual({ addr: 10, end: 15, type: 'a' }); 52 | }); 53 | 54 | // === 55 | 56 | test("alloc between 0 and 1, attached with 1", () => { 57 | const result = layer.alloc(25, 5, 'a'); 58 | expect(result.length).toEqual(2); 59 | expect(result.regions[1]).toEqual({ addr: 25, end: 40, type: 'a' }); 60 | }); 61 | 62 | test("alloc between 0 and 1, overwrapped with 1", () => { 63 | const result = layer.alloc(25, 10, 'a'); 64 | expect(result.length).toEqual(2); 65 | expect(result.regions[1]).toEqual({ addr: 25, end: 40, type: 'a' }); 66 | }); 67 | 68 | test("alloc between 0 and 1, overwrapped with 1 but different type", () => { 69 | const result = layer.alloc(25, 10, 'b'); 70 | expect(result.length).toEqual(3); 71 | expect(result.regions[1]).toEqual({ addr: 25, end: 35, type: 'b' }); 72 | expect(result.regions[2]).toEqual({ addr: 35, end: 40, type: 'a' }); 73 | }); 74 | 75 | // === 76 | 77 | test("merging whole regions", () => { 78 | const result = layer.alloc(20, 10, 'a'); 79 | expect(result.length).toEqual(1); 80 | expect(result.regions[0]).toEqual({ addr: 10, end: 40, type: 'a' }); 81 | }); 82 | 83 | 84 | // === 85 | 86 | test("case", () => { 87 | const layer = new FlexibleLayer('mmap', config); 88 | layer.regions = [ 89 | {addr: 0x200f50000, end: 0x200f60000, type: 'anon'}, 90 | {addr: 0x200f64000, end: 0x200f70000, type: 'anon'}, 91 | {addr: 0x201000000, end: 0x20110c000, type: 'anon'}, 92 | {addr: 0x201200000, end: 0x201300000, type: 'anon'}, 93 | ]; 94 | // 76: a 10 101928 mmap 0x201204000 16384 free 95 | const result = layer.alloc(0x201204000, 16384, 'free'); 96 | expect(result.regions[3].end).toEqual(0x201204000); 97 | expect(result.length).toEqual(6); 98 | expect(result.regions[4].end).toEqual(0x201204000 + 16384); 99 | expect(result.regions[4].type).toEqual('free'); 100 | expect(result.regions[5]).toEqual({ addr: 0x201204000 + 16384, end: 0x201300000, type: 'anon' }); 101 | }); 102 | }); 103 | -------------------------------------------------------------------------------- /src/app/App.svelte: -------------------------------------------------------------------------------- 1 | 89 | 90 | 120 | 121 | {#if memlog} 122 | 123 | {#if showMemLog} 124 | 125 | 126 | 129 | 130 |