Skip to content
Open
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
61 changes: 61 additions & 0 deletions src/native/clr/host/fastdev-assemblies.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
#include <cerrno>
#include <cstring>
#include <limits>
#include <string>

#include <constants.hh>
#include <xamarin-app.hh>
#include <host/fastdev-assemblies.hh>
#include <runtime-base/android-system.hh>
#include <runtime-base/util.hh>
Expand Down Expand Up @@ -111,3 +113,62 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si

return reinterpret_cast<void*>(buffer);
}

auto FastDevAssemblies::build_tpa_list (std::string &tpa_list) noexcept -> bool
{
tpa_list.clear ();

std::string const& override_dir_path = AndroidSystem::get_primary_override_dir ();
if (!Util::dir_exists (override_dir_path)) {
return false;
}

DIR *dir = opendir (override_dir_path.c_str ());
if (dir == nullptr) {
log_warn (LOG_ASSEMBLY, "FastDev: failed to open override dir '{}'. {}"sv, override_dir_path, std::strerror (errno));
return false;
}
int dir_fd = dirfd (dir);
if (dir_fd < 0) {
log_warn (LOG_ASSEMBLY, "FastDev: failed to obtain fd for override dir '{}'. {}"sv, override_dir_path, std::strerror (errno));
closedir (dir);
return false;
}

size_t count = 0;
// NOTE: The TPA list is sourced from `type_map_unique_assemblies`, which is
// only populated when `_AndroidTypeMapImplementation=llvm-ir` (the Debug
// default). With `managed` or `trimmable` typemaps the native typemap is
// empty, so no TPA paths are added and stack frames won't carry file/line
// info even under FastDev.
uint64_t expected_count = type_map.unique_assemblies_count;
Comment thread
jonathanpeppers marked this conversation as resolved.
for (uint64_t i = 0; i < expected_count; i++) {
TypeMapAssembly const &asm_entry = type_map_unique_assemblies[i];
std::string_view name {
&type_map_assembly_names[asm_entry.name_offset],
static_cast<size_t>(asm_entry.name_length)
};

// `Name` is the simple assembly name (e.g. "Mono.Android"), no extension.
std::string file_name;
file_name.reserve (name.size () + 4);
file_name.append (name);
file_name.append (".dll");

if (!Util::file_exists (dir_fd, file_name)) {
continue;
}

if (!tpa_list.empty ()) {
tpa_list.append (":");
}
tpa_list.append (override_dir_path);
tpa_list.append ("/");
tpa_list.append (file_name);
count++;
}
closedir (dir);

log_debug (LOG_ASSEMBLY, "FastDev: built TPA list with {} assemblies from '{}'"sv, count, override_dir_path);
return count > 0;
}
37 changes: 34 additions & 3 deletions src/native/clr/host/host.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <unistd.h>

#include <android/looper.h>
Expand Down Expand Up @@ -450,12 +452,41 @@ void Host::Java_mono_android_Runtime_initInternal (
// The first entry in the property arrays is for the host contract pointer. Application build makes sure
// of that.
init_runtime_property_values[0] = host_contract_ptr_buffer.data ();

const char **prop_names = init_runtime_property_names;
const char **prop_values = const_cast<const char**>(init_runtime_property_values);
int prop_count = static_cast<int>(application_config.number_of_runtime_properties);

// In Debug builds with FastDev, append `TRUSTED_PLATFORM_ASSEMBLIES` with full
// paths to the assemblies pushed into `.__override__/<arch>/`. CoreCLR then
// opens those files from disk so `Assembly.Location` is populated and
// `StackTraceSymbols` can find sibling `.pdb` files for runtime-rendered
// managed stack traces (file/line).
if constexpr (Constants::is_debug_build) {
// Storage must outlive `coreclr_initialize`; function-local statics
// give us process lifetime without polluting global namespace.
static std::string fastdev_tpa_list;
static std::vector<const char*> fastdev_prop_names;
static std::vector<const char*> fastdev_prop_values;

if (FastDevAssemblies::build_tpa_list (fastdev_tpa_list)) {
fastdev_prop_names.assign (prop_names, prop_names + prop_count);
fastdev_prop_values.assign (prop_values, prop_values + prop_count);
fastdev_prop_names.push_back (HOST_PROPERTY_TRUSTED_PLATFORM_ASSEMBLIES);
fastdev_prop_values.push_back (fastdev_tpa_list.c_str ());

prop_names = fastdev_prop_names.data ();
prop_values = fastdev_prop_values.data ();
prop_count = static_cast<int>(fastdev_prop_names.size ());
}
}

int hr = FastTiming::time_call ("coreclr_initialize"sv, coreclr_initialize,
application_config.android_package_name,
"Xamarin.Android",
(int)application_config.number_of_runtime_properties,
init_runtime_property_names,
const_cast<const char**>(init_runtime_property_values),
prop_count,
prop_names,
prop_values,
&clr_host,
&domain_id
);
Expand Down
7 changes: 7 additions & 0 deletions src/native/clr/include/host/fastdev-assemblies.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include <cstdint>
#include <mutex>
#include <string>
#include <string_view>

namespace xamarin::android {
Expand All @@ -12,11 +13,17 @@ namespace xamarin::android {
public:
#if defined(DEBUG)
static auto open_assembly (std::string_view const& name, int64_t &size) noexcept -> void*;
static auto build_tpa_list (std::string &tpa_list) noexcept -> bool;
#else
static auto open_assembly ([[maybe_unused]] std::string_view const& name, [[maybe_unused]] int64_t &size) noexcept -> void*
{
return nullptr;
}

static auto build_tpa_list ([[maybe_unused]] std::string &tpa_list) noexcept -> bool
{
return false;
}
#endif

private:
Expand Down
50 changes: 50 additions & 0 deletions tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using System.Xml.XPath;
Expand Down Expand Up @@ -2227,6 +2228,55 @@ public void FastDeployEnvironmentFiles ([Values] bool isRelease, [Values] bool e
}
}

[Test]
public void StackTraceContainsLineNumbers ()
{
// FastDev (Debug + assemblies on disk in .__override__) wires up
// portable PDB lookup for runtime-rendered stack traces on CoreCLR
// via the TPA list passed to coreclr_initialize.
AndroidRuntime runtime = AndroidRuntime.CoreCLR;
if (IgnoreUnsupportedConfiguration (runtime, release: false)) {
return;
}

var proj = new XamarinAndroidApplicationProject (packageName: PackageUtils.MakePackageName (runtime)) {
ProjectName = nameof (StackTraceContainsLineNumbers),
RootNamespace = nameof (StackTraceContainsLineNumbers),
IsRelease = false,
EmbedAssembliesIntoApk = false,
EnableDefaultItems = true,
};
proj.SetRuntime (runtime);
proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", """
Console.WriteLine ("#STACKTRACE-BEGIN#");
Console.WriteLine (Environment.StackTrace);
Console.WriteLine ("#STACKTRACE-END#");
""");
using var builder = CreateApkBuilder ();
Assert.IsTrue (builder.Install (proj), "App should have installed.");
RunProjectAndAssert (proj, builder);

var appStartupLogcatFile = Path.Combine (Root, builder.ProjectDirectory, "stacktrace-logcat.log");
Assert.IsTrue (
MonitorAdbLogcat (line => line.Contains ("#STACKTRACE-END#"), appStartupLogcatFile, timeout: 60),
"Stack trace end marker not found in logcat (output may be missing or truncated)."
);

var logcatOutput = File.ReadAllText (appStartupLogcatFile);
Comment thread
jonathanpeppers marked this conversation as resolved.
StringAssert.Contains ("#STACKTRACE-BEGIN#", logcatOutput, "Stack trace start marker not found in logcat");

// Expect a frame in MainActivity.OnCreate to include
// "in <path>MainActivity.cs:line <N>" on a single line.
var match = Regex.Match (
logcatOutput,
@"at\s+\S*MainActivity\.OnCreate.*\sin\s+\S+MainActivity\.cs:line\s+\d+"
);
Assert.IsTrue (
match.Success,
$"Expected MainActivity.OnCreate frame to include file/line info. Logcat:\n{logcatOutput}"
);
}

[Test]
public void DotNetRunEnvironmentVariables ()
{
Expand Down
Loading