dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.27k stars 4.73k forks source link

Process.Modules does not list dynamically loaded assemblies on Linux #64042

Open kevingosse opened 2 years ago

kevingosse commented 2 years ago

For a diagnostic tool I'm writing, I need to check if a target process has loaded a given assembly. To do so, I retrieve the Process instance, then check the contents of Process.Modules. This works on Windows, but on Linux I can't find the assembly even though it's definitely loaded in the target process.

After digging further, it seems that the module is listed in /proc/<pid>/maps as expected, but it has the following permissions:

7f98d5dc2000-7f98d5fd1000 r--s 00000000 08:40 127037                     /project/tracer/bin/tracer-home/netcoreapp3.1/Datadog.Trace.dll

Interop.procfs.ParseMapsModulesCore only adds the module if it has the 'execute' permission:

https://github.com/dotnet/runtime/blob/fd33b9eac6f259ef731e0204bcd2aebd534b8ddc/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs#L85-L93

It looks like the execution flag is not set when the assembly is loaded at runtime (for instance with Assembly.LoadFrom or AssemblyLoadContext.LoadFromAssemblyPath), and therefore it won't be listed in the modules.

I'm not sure what to make of that, this behavior has surprised me so I wanted to bring it to your attention. On my side I'll just manually parse the maps file.

ghost commented 2 years ago

Tagging subscribers to this area: @tommcdon See info in area-owners.md if you want to be subscribed.

Issue Details
For a diagnostic tool I'm writing, I need to check if a target process has loaded a given assembly. To do so, I retrieve the `Process` instance, then check the contents of `Process.Modules`. This works on Windows, but on Linux I can't find the assembly even though it's definitely loaded in the target process. After digging further, it seems that the module is listed in `/proc//maps` as expected, but it has the following permissions: ``` 7f98d5dc2000-7f98d5fd1000 r--s 00000000 08:40 127037 /project/tracer/bin/tracer-home/netcoreapp3.1/Datadog.Trace.dll ``` `Interop.procfs.ParseMapsModulesCore` only adds the module if it has the 'execute' permission: https://github.com/dotnet/runtime/blob/fd33b9eac6f259ef731e0204bcd2aebd534b8ddc/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs#L85-L93 It looks like the execution flag is not set when the assembly is loaded at runtime (with for instance with `Assembly.LoadFrom` or `AssemblyLoadContext.LoadFromAssemblyPath`), and therefore it won't be listed in the modules. I'm not sure what to make of that, this behavior has surprised me so I wanted to bring it to your attention. On my side I'll just manually parse the maps file.
Author: kevingosse
Assignees: -
Labels: `area-Diagnostics-coreclr`, `untriaged`
Milestone: -
jkotas commented 2 years ago

Dup of https://github.com/dotnet/runtime/issues/24768 ?

ghost commented 2 years ago

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process See info in area-owners.md if you want to be subscribed.

Issue Details
For a diagnostic tool I'm writing, I need to check if a target process has loaded a given assembly. To do so, I retrieve the `Process` instance, then check the contents of `Process.Modules`. This works on Windows, but on Linux I can't find the assembly even though it's definitely loaded in the target process. After digging further, it seems that the module is listed in `/proc//maps` as expected, but it has the following permissions: ``` 7f98d5dc2000-7f98d5fd1000 r--s 00000000 08:40 127037 /project/tracer/bin/tracer-home/netcoreapp3.1/Datadog.Trace.dll ``` `Interop.procfs.ParseMapsModulesCore` only adds the module if it has the 'execute' permission: https://github.com/dotnet/runtime/blob/fd33b9eac6f259ef731e0204bcd2aebd534b8ddc/src/libraries/Common/src/Interop/Linux/procfs/Interop.ProcFsStat.ParseMapModules.cs#L85-L93 It looks like the execution flag is not set when the assembly is loaded at runtime (for instance with `Assembly.LoadFrom` or `AssemblyLoadContext.LoadFromAssemblyPath`), and therefore it won't be listed in the modules. I'm not sure what to make of that, this behavior has surprised me so I wanted to bring it to your attention. On my side I'll just manually parse the maps file.
Author: kevingosse
Assignees: -
Labels: `area-System.Diagnostics.Process`, `untriaged`
Milestone: -
kevingosse commented 2 years ago

Dup of #24768 ?

The entry point is listed in the modules as far as I can tell, so I think it's a separate problem 🤔 Though #24768 is pretty old, so maybe it was hitting the same issue when applications were started with dotnet app.dll and therefore the entrypoint was loaded dynamically.

jkotas commented 2 years ago

it was hitting the same issue when applications were started with dotnet app.dll and therefore the entrypoint was loaded dynamically.

I am pretty sure that it was the case.

kevingosse commented 2 years ago

Makes sense. Do you want me to close this issue and move the information to the other one?

adamsitnik commented 2 years ago

cc @tmds

tmds commented 2 years ago

// we only add module to collection, if at least one row had 'r' and 'x' set.

I guess an assembly that has no native code won't map with the 'x' bit set, and an assembly that has native code (e.g. cross-genned) will?

We could consider to include all files that end with .dll even if they don't have the 'x' set.

cc @janvorli

tmds commented 2 years ago

We could consider to include all files that end with .dll even if they don't have the 'x' set.

@janvorli @jkotas what do you think?

jkotas commented 2 years ago

The current implementation approach is always going to be unreliable on non-Windows for listing managed modules loaded into the process. I do not have an opinion on whether it is better to have more false positives or false negatives. Resolving this issue as won't fix would be fine with me as well.

The reliable way to get accurate list of managed modules loaded into the process would be via tracing/eventpipe or via debugger apis. It feels too heavy weight to take dependency on those in core framework to implement this API. We have separate libraries for that (e.g. https://github.com/microsoft/clrmd).

tmds commented 2 years ago

I do not have an opinion on whether it is better to have more false positives or false negatives.

Because most apps don't map dlls themselves, we will mostly be adding valid dlls. So I have a slight preference for including them.