Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: show dictionary items #21

Merged
merged 1 commit into from
Feb 1, 2024
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
26 changes: 21 additions & 5 deletions src/DebugHost/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,33 @@

using Microsoft.Diagnostics.Runtime;

foreach (var dumpPath in Directory.GetFiles(@"D:\dumps", "*.dmp"))
{
ProcessFile(dumpPath);
}
// foreach (var dumpPath in Directory.GetFiles(@"D:\dumps", "*.dmp"))
// {
// ProcessFile(dumpPath);
// }

ProcessFile(@"D:\dbg\dump_20230507_155200.dmp");

static void ProcessFile(string filePath)
{
Console.WriteLine($"Processing dump: {filePath}");

var runtimeContext = new RuntimeContext(filePath);
WriteWebRequests(runtimeContext);
WriteDictionary(runtimeContext);

void WriteDictionary(RuntimeContext runtimeContext1)
{
var obj = runtimeContext.EnumerateObjects(null)
.Where(obj => !obj.IsNull && obj.Type.Name.StartsWith("System.Collections.Generic.Dictionary<System.String"))
.FirstOrDefault();

DictionaryProxy dictionaryProxy = new DictionaryProxy(runtimeContext, obj);
foreach (var kvp in dictionaryProxy.EnumerateItems())
{
Console.WriteLine($"{kvp.Key.Address:x16} = {kvp.Value.Address:x16}");
}
}
// WriteWebRequests(runtimeContext);

static void WriteWebRequests(RuntimeContext runtimeContext)
{
Expand Down
107 changes: 104 additions & 3 deletions src/Heartbeat.Runtime/Proxies/DictionaryProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,61 @@ namespace Heartbeat.Runtime.Proxies;

public class DictionaryProxy: ProxyBase, ILoggerDump
{
public int Count => TargetObject.ReadField<int>("count");
public int Count => TargetObject.ReadField<int>(CountFieldName);
public int Version => TargetObject.ReadField<int>("version");
// TODO add TKey
// TODO add TValue
public ulong KeyMethodTable { get; }
public ulong ValueMethodTable { get; }

public DictionaryProxy(RuntimeContext context, ClrObject targetObject)
: base(context, targetObject)
{
(KeyMethodTable, ValueMethodTable) = GetMethodTables();
}

public DictionaryProxy(RuntimeContext context, ulong address)
: base(context, address)
{
(KeyMethodTable, ValueMethodTable) = GetMethodTables();
}

private string CountFieldName => Context.IsCoreRuntime ? "_count" : "count";
private string EntriesFieldName => Context.IsCoreRuntime ? "_entries" : "entries";

private (ulong, ulong) GetMethodTables()
{
// ulong keyMt = 0;
// ulong valueMt = 0;
//
// foreach (var genericParameter in TargetObject.Type!.EnumerateGenericParameters())
// {
// if (keyMt == 0)
// {
// foreach (var module in Context.Heap.Runtime.EnumerateModules())
// {
// foreach ((ulong methodTable, int token) in module.EnumerateTypeRefToMethodTableMap())
// {
// if (token == genericParameter.MetadataToken)
// {
// keyMt = methodTable;
// }
// }
// }
// }
// }

ClrObject entries = TargetObject.ReadObjectField(EntriesFieldName);
if (entries.IsNull)
{
return (0, 0);
}
var entriesArray = entries
.AsArray();

var nextField = entriesArray.Type.ComponentType!.GetFieldByName("next")!;
var keyField = entriesArray.Type.ComponentType!.GetFieldByName("key")!;
var valueField = entriesArray.Type.ComponentType!.GetFieldByName("value")!;

return (keyField.Type.MethodTable, valueField.Type.MethodTable);
}

private IEnumerable<KeyValuePair<TKey, TValue>> EnumerateKeyValuePairs<TKey, TValue>(Func<ulong, ClrInstanceField, ClrInstanceField, KeyValuePair<TKey, TValue>> kvpBuilder)
Expand Down Expand Up @@ -54,6 +96,63 @@ private IEnumerable<KeyValuePair<TKey, TValue>> EnumerateKeyValuePairs<TKey, TVa
}
}

public IEnumerable<KeyValuePair<Item, Item>> EnumerateItems()
{
int count = TargetObject.ReadField<int>(Context.IsCoreRuntime ? "_count" : "count");
if (count == 0)
yield break;

var entries = TargetObject.ReadObjectField(EntriesFieldName)
.AsArray();

var nextField = entries.Type.ComponentType!.GetFieldByName("next")!;
var keyField = entries.Type.ComponentType!.GetFieldByName("key")!;
var valueField = entries.Type.ComponentType!.GetFieldByName("value")!;

// TODO return
// keyField.Type.MethodTable
// valueField.Type.MethodTable

for (int entryIndex = 0; entryIndex < count; entryIndex++)
{
var entry = entries.GetStructValue(entryIndex);

int next = nextField.Read<int>(entry.Address, true);
if (next < -1)
{
continue;
}

Item key;
Item value;
if (keyField.IsObjectReference)
{
// IClrValue
var entryKey = keyField.ReadObject(entry.Address, true);
key = new Item(entryKey.Address, entryKey.Type?.IsString ?? false ? entryKey.AsString() : null);
}
else
{
var entryKey = keyField.ReadStruct(entry.Address, true);
key = new Item(entryKey.Address, null);
}

if (valueField.IsObjectReference)
{
// IClrValue
var entryValue = valueField.ReadObject(entry.Address, true);
value = new Item(entryValue.Address, entryValue.Type?.IsString ?? false ? entryValue.AsString() : null);
}
else
{
var entryValue = valueField.ReadStruct(entry.Address, true);
value = new Item(entryValue.Address, null);
}

yield return new KeyValuePair<Item, Item>(key, value);
}
}

private KeyValuePair<string, string?> ReadStringString(
ulong elementAddress,
ClrInstanceField keyField,
Expand Down Expand Up @@ -107,4 +206,6 @@ public void Dump<TKey, TValue>(ILogger logger)
Console.WriteLine($"{kvp.Key} = {kvp.Value}");
}
}

public record struct Item(ulong Address, string? Value);
}
79 changes: 79 additions & 0 deletions src/Heartbeat/ClientApp/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,40 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}/as-dictionary':
get:
tags:
- Dump
operationId: GetClrObjectAsDictionary
parameters:
- name: address
in: path
required: true
style: simple
schema:
type: integer
format: int64
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/DictionaryInfo'
'204':
description: No Content
'404':
description: Not Found
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
'500':
description: Internal Server Error
content:
application/json:
schema:
$ref: '#/components/schemas/ProblemDetails'
'/api/dump/object/{address}/as-jwt':
get:
tags:
Expand Down Expand Up @@ -568,6 +602,51 @@ components:
- AsyncPinnedHandle
- SizedRefHandle
type: string
DictionaryInfo:
required:
- count
- items
- keyMethodTable
- valueMethodTable
type: object
properties:
count:
type: integer
format: int32
keyMethodTable:
type: integer
format: int64
valueMethodTable:
type: integer
format: int64
items:
type: array
items:
$ref: '#/components/schemas/DictionaryItemDictionaryItemKeyValuePair'
additionalProperties: false
DictionaryItem:
required:
- address
type: object
properties:
address:
type: integer
format: int64
value:
type: string
nullable: true
additionalProperties: false
DictionaryItemDictionaryItemKeyValuePair:
required:
- key
- value
type: object
properties:
key:
$ref: '#/components/schemas/DictionaryItem'
value:
$ref: '#/components/schemas/DictionaryItem'
additionalProperties: false
DumpInfo:
required:
- architecture
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* tslint:disable */
/* eslint-disable */
// Generated by Microsoft Kiota
import { createDictionaryInfoFromDiscriminatorValue, createProblemDetailsFromDiscriminatorValue, deserializeIntoProblemDetails, serializeProblemDetails, type DictionaryInfo, type ProblemDetails } from '../../../../../models/';
import { BaseRequestBuilder, HttpMethod, RequestInformation, type Parsable, type ParsableFactory, type RequestAdapter, type RequestConfiguration, type RequestOption } from '@microsoft/kiota-abstractions';

/**
* Builds and executes requests for operations under /api/dump/object/{address}/as-dictionary
*/
export class AsDictionaryRequestBuilder extends BaseRequestBuilder<AsDictionaryRequestBuilder> {
/**
* Instantiates a new AsDictionaryRequestBuilder and sets the default values.
* @param pathParameters The raw url or the Url template parameters for the request.
* @param requestAdapter The request adapter to use to execute the requests.
*/
public constructor(pathParameters: Record<string, unknown> | string | undefined, requestAdapter: RequestAdapter) {
super(pathParameters, requestAdapter, "{+baseurl}/api/dump/object/{address}/as-dictionary", (x, y) => new AsDictionaryRequestBuilder(x, y));
}
/**
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a Promise of DictionaryInfo
*/
public get(requestConfiguration?: RequestConfiguration<object> | undefined) : Promise<DictionaryInfo | undefined> {
const requestInfo = this.toGetRequestInformation(
requestConfiguration
);
const errorMapping = {
"404": createProblemDetailsFromDiscriminatorValue,
"500": createProblemDetailsFromDiscriminatorValue,
} as Record<string, ParsableFactory<Parsable>>;
return this.requestAdapter.sendAsync<DictionaryInfo>(requestInfo, createDictionaryInfoFromDiscriminatorValue, errorMapping);
}
/**
* @param requestConfiguration Configuration for the request such as headers, query parameters, and middleware options.
* @returns a RequestInformation
*/
public toGetRequestInformation(requestConfiguration?: RequestConfiguration<object> | undefined) : RequestInformation {
const requestInfo = new RequestInformation(HttpMethod.GET, this.urlTemplate, this.pathParameters);
requestInfo.configure(requestConfiguration);
requestInfo.headers.tryAdd("Accept", "application/json");
return requestInfo;
}
}
/* tslint:enable */
/* eslint-enable */
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Generated by Microsoft Kiota
import { createGetClrObjectResultFromDiscriminatorValue, createProblemDetailsFromDiscriminatorValue, deserializeIntoProblemDetails, serializeProblemDetails, type GetClrObjectResult, type ProblemDetails } from '../../../../models/';
import { AsArrayRequestBuilder } from './asArray/';
import { AsDictionaryRequestBuilder } from './asDictionary/';
import { AsJwtRequestBuilder } from './asJwt/';
import { FieldsRequestBuilder } from './fields/';
import { RootsRequestBuilder } from './roots/';
Expand All @@ -18,6 +19,12 @@ export class WithAddressItemRequestBuilder extends BaseRequestBuilder<WithAddres
public get asArray(): AsArrayRequestBuilder {
return new AsArrayRequestBuilder(this.pathParameters, this.requestAdapter);
}
/**
* The asDictionary property
*/
public get asDictionary(): AsDictionaryRequestBuilder {
return new AsDictionaryRequestBuilder(this.pathParameters, this.requestAdapter);
}
/**
* The asJwt property
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Heartbeat/ClientApp/src/client/kiota-lock.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"descriptionHash": "D2636368E24B63D166B3F432C5AD1C9DD4B984EEE69C76339F31F181C38DE793AA1741B79A96DF5B0CBCD8D84501856A18931CC90AB365F2A17465A3A57A9461",
"descriptionHash": "B5A6597B503DFBDF748026FBD234968A4A01A1C687D00711E5A910900C066A238295C318D98C4968DAA9AC5E2C494CFCB8CB95F0093A95E2DCAED37AC072D9C7",
"descriptionLocation": "..\\..\\api.yml",
"lockFileVersion": "1.0.0",
"kiotaVersion": "1.10.1",
Expand Down
Loading
Loading