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
92 changes: 92 additions & 0 deletions src/coreclr/vm/debugdebugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,89 @@
#include "corsym.h"
#include "generics.h"
#include "stackwalk.h"
#include "hostinformation.h"

#define PORTABLE_PDB_MINOR_VERSION 20557
#define IMAGE_DEBUG_TYPE_EMBEDDED_PORTABLE_PDB 17

#ifndef DACCESS_COMPILE

#ifdef TARGET_BROWSER
static void TryLoadInMemorySymbols(Module* pModule)
{
STANDARD_VM_CONTRACT;

PEAssembly* pAssembly = pModule->GetPEAssembly();
if (!HostInformation::HasExternalProbe() || !pAssembly->HasLoadedPEImage())
return;

PEImageLayout* pLayout = pAssembly->GetLoadedLayout();
if (!pLayout->HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_DEBUG))
return;

COUNT_T debugDirectorySize = 0;
IMAGE_DEBUG_DIRECTORY* entries = reinterpret_cast<IMAGE_DEBUG_DIRECTORY*>(
pLayout->GetDirectoryEntryData(IMAGE_DIRECTORY_ENTRY_DEBUG, &debugDirectorySize));
if (entries == nullptr || debugDirectorySize % sizeof(IMAGE_DEBUG_DIRECTORY) != 0)
return;

for (COUNT_T i = 0; i < debugDirectorySize / sizeof(IMAGE_DEBUG_DIRECTORY); i++)
{
const IMAGE_DEBUG_DIRECTORY& entry = entries[i];
if (entry.Type != IMAGE_DEBUG_TYPE_CODEVIEW || entry.MinorVersion != PORTABLE_PDB_MINOR_VERSION)
continue;

RVA rva = entry.AddressOfRawData != 0 ? entry.AddressOfRawData : pLayout->OffsetToRva(entry.PointerToRawData);
if (entry.SizeOfData <= offsetof(CV_INFO_PDB70, path) || !pLayout->CheckRva(rva, entry.SizeOfData))
return;

const CV_INFO_PDB70* codeView = reinterpret_cast<const CV_INFO_PDB70*>(pLayout->GetRvaData(rva));
if (codeView->magic != CV_SIGNATURE_RSDS)
return;

size_t pathSize = entry.SizeOfData - offsetof(CV_INFO_PDB70, path);
size_t pathLength = strnlen(codeView->path, pathSize);
if (pathLength == pathSize)
return;

// CodeView paths can come from either Windows or Unix, regardless of the runtime platform.
const char* fileName = codeView->path;
for (size_t j = 0; j < pathLength; j++)
{
if (codeView->path[j] == '/' || codeView->path[j] == '\\')
fileName = codeView->path + j + 1;
}

if (*fileName == '\0')
return;

StackSString pdbPath;
const SString& assemblyPath = pAssembly->GetIdentityPath();
SString::CIterator directoryEnd = assemblyPath.End();
if (assemblyPath.FindBack(directoryEnd, DIRECTORY_SEPARATOR_CHAR_W))
{
directoryEnd++;
pdbPath.Set(assemblyPath, assemblyPath.Begin(), directoryEnd);
}
pdbPath.AppendUTF8(fileName);

void* pdbData = nullptr;
int64_t pdbSize = 0;
if (!HostInformation::ExternalAssemblyProbe(pdbPath, &pdbData, &pdbSize))
return;

if (pdbData == nullptr || pdbSize <= 0 || pdbSize > INT32_MAX)
COMPlusThrowHR(COR_E_BADIMAGEFORMAT);

GCX_PREEMP();
ReleaseHolder<CGrowableStream> symbolStream(new CGrowableStream(1.0f, 1));
IfFailThrow(symbolStream->Write(pdbData, static_cast<ULONG>(pdbSize), nullptr));
pModule->SetInMemorySymbolStream(symbolStream);
return;
}
}
#endif // TARGET_BROWSER

//
// Notes:
// If a managed debugger is attached, this should send the managed UserBreak event.
Expand Down Expand Up @@ -791,6 +868,13 @@ extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal(

// If there is a in memory symbol stream
CGrowableStream* stream = pModule->GetInMemorySymbolStream();
#ifdef TARGET_BROWSER
if (stream == nullptr)
{
TryLoadInMemorySymbols(pModule);
stream = pModule->GetInMemorySymbolStream();
}
#endif // TARGET_BROWSER
if (stream != NULL)
{
MemoryRange range = stream->GetRawBuffer();
Expand All @@ -802,6 +886,14 @@ extern "C" void QCALLTYPE StackTrace_GetStackFramesInternal(
CLR_I4 *pInMemoryPdbSize = (CLR_I4 *)((I4ARRAYREF)gc.pStackFrameHelper->rgiInMemoryPdbSize)->GetDirectPointerToNonObjectElements();
pInMemoryPdbSize[iNumValidFrames] = (CLR_I4)range.Size();
}
#ifdef TARGET_BROWSER
else if (pPEAssembly->HasLoadedPEImage() && pPEAssembly->GetLoadedLayout()->IsWebcilFormat())
{
// PEReader cannot read Webcil. A failed lookup must not prevent symbol lookup for other frames.
CLR_I4* methodTokens = (CLR_I4*)((I4ARRAYREF)gc.pStackFrameHelper->rgiMethodToken)->GetDirectPointerToNonObjectElements();
methodTokens[iNumValidFrames] = 0;
}
#endif // TARGET_BROWSER
else
{
// Set the pdb path (assembly file name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.IO;
using System.Runtime.CompilerServices;
using Xunit;

namespace System.Diagnostics.SymbolStore.Tests
Expand All @@ -16,9 +17,41 @@ public void StackTraceSymbolsDoNotLockFile()
var asmPath = AssemblyPathHelper.GetAssemblyLocation(typeof(StackTraceSymbolsTests).Assembly);
var pdbPath = Path.ChangeExtension(asmPath, ".pdb");

Assert.True(File.Exists(pdbPath));
bool fileBackedSymbols = !PlatformDetection.IsBrowser;
Assert.Equal(fileBackedSymbols, File.Exists(pdbPath));
new StackTrace(true).GetFrames();
File.Move(pdbPath, pdbPath);
if (fileBackedSymbols)
{
File.Move(pdbPath, pdbPath);
}
}

[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser))]
public void StackTraceSymbolsFromMemory()
{
string assemblyPath = AssemblyPathHelper.GetAssemblyLocation(typeof(StackTraceSymbolsTests).Assembly);
Assert.False(File.Exists(Path.ChangeExtension(assemblyPath, ".pdb")));

StackFrame frameWithoutSymbols = CaptureFrame(needFileInfo: false);
Assert.Null(frameWithoutSymbols.GetFileName());
Assert.Equal(0, frameWithoutSymbols.GetFileLineNumber());

for (int i = 0; i < 2; i++)
{
StackFrame frame = CaptureFrame(needFileInfo: true);
Assert.Equal(nameof(CaptureFrame), frame.GetMethod().Name);
Assert.True(frame.GetILOffset() >= 0, $"Expected an IL offset for {frame}.");
Assert.Equal("InMemorySymbols.cs", Path.GetFileName(frame.GetFileName()?.Replace('\\', '/')));
Assert.Equal(123, frame.GetFileLineNumber());
}
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static StackFrame CaptureFrame(bool needFileInfo)
{
#line 123 "InMemorySymbols.cs"
return new StackFrame(needFileInfo);
#line default
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<!-- these tests depend on the pdb files -->
<DebuggerSupport Condition="'$(DebuggerSupport)' == '' and '$(TargetOS)' == 'browser'">true</DebuggerSupport>
<StackTraceLineNumberSupport Condition="'$(StackTraceLineNumberSupport)' == '' and '$(TargetOS)' == 'browser'">true</StackTraceLineNumberSupport>
<WasmDebugLevel Condition="'$(WasmDebugLevel)' == '' and '$(TargetOS)' == 'browser'">1</WasmDebugLevel>
<!-- https://github.com/dotnet/runtime/issues/126862 -->
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
<EnableAotAnalyzer>false</EnableAotAnalyzer>
Expand All @@ -27,7 +28,6 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetOS)' == 'browser'">
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).dll" />
<WasmFilesToIncludeFromPublishDir Include="$(AssemblyName).pdb" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="ExceptionTestAssembly\ExceptionTestAssembly.csproj" />
Expand Down
39 changes: 23 additions & 16 deletions src/native/libs/Common/JavaScript/host/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@ const loadedAssemblies: Map<string, { ptr: number, length: number }> = new Map()
// Self-installing wrapper version; keep in sync with WebcilConstants.WASM_WRAPPER_VERSION_SELF_INSTALLING.
const webcilWrapperVersion = 2;

export function registerPdbBytes(bytes: Uint8Array, virtualPath: string) {
const lastSlash = virtualPath.lastIndexOf("/");
let parentDirectory = lastSlash > 0
? virtualPath.substring(0, lastSlash)
: browserVirtualAppBase;
let fileName = lastSlash > 0 ? virtualPath.substring(lastSlash + 1) : virtualPath;
if (fileName.startsWith("/")) {
fileName = fileName.substring(1);
}
if (!parentDirectory.startsWith("/")) {
parentDirectory = browserVirtualAppBase + parentDirectory;
}
export function registerPdbBytes(bytes: Uint8Array, virtualPath: string, shortName: string) {
const sp = _ems_.stackSave();
try {
const ptrPtr = _ems_.stackAlloc(sizeOfPtr);
if (_ems_._posix_memalign(ptrPtr as any, 16, bytes.length)) {
throw new Error("posix_memalign failed for PDB");
}

_ems_.dotnetLogger.debug(`Registering PDB '${fileName}' in directory '${parentDirectory}'`);
_ems_.FS.createPath("/", parentDirectory, true, true);
_ems_.FS.createDataFile(parentDirectory, fileName, bytes, true /* canRead */, true /* canWrite */, true /* canOwn */);
const ptr = _ems_.HEAPU32[ptrPtr as any >>> 2];
_ems_.HEAPU8.set(bytes, ptr >>> 0);

_ems_.dotnetLogger.debug(`Registered PDB '${virtualPath}' (shortName: '${shortName}') at ${ptr.toString(16)} length ${bytes.length}`);
loadedAssemblies.set(virtualPath, { ptr, length: bytes.length });
loadedAssemblies.set(shortName, { ptr, length: bytes.length });
} finally {
_ems_.stackRestore(sp);
}
}

export function registerDllBytes(bytes: Uint8Array, virtualPath: string, shortName: string) {
Expand Down Expand Up @@ -170,7 +171,13 @@ function finishWebcilInstance(instance: WebAssembly.Instance, payloadPtr: number

export function BrowserHost_ExternalAssemblyProbe(pathPtr: CharPtr, outDataStartPtr: VoidPtrPtr, outSize: VoidPtr): boolean {
const path = _ems_.UTF8ArrayToString(_ems_.dotnetApi.localHeapViewU8(), pathPtr as any);
const assembly = loadedAssemblies.get(path);
let assembly = loadedAssemblies.get(path);
if (!assembly) {
const lastSlash = path.lastIndexOf("/");
if (lastSlash >= 0) {
assembly = loadedAssemblies.get(path.substring(lastSlash + 1));
}
}
if (assembly) {
_ems_.HEAPU32[outDataStartPtr as any >>> 2] = assembly.ptr;
// int64_t target
Expand Down
2 changes: 1 addition & 1 deletion src/native/libs/Common/JavaScript/loader/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export async function fetchPdb(asset: AssemblyAsset): Promise<void> {
}
if (bytes) {
await nativeModulePromiseController.promise;
dotnetBrowserHostExports.registerPdbBytes(bytes, assetInternal.virtualPath);
dotnetBrowserHostExports.registerPdbBytes(bytes, assetInternal.virtualPath, assetInternal.shortName!);
}
}

Expand Down
Loading