Making singlefile apps work when wrapped in an OSX universal binary (#52997)
authorVladimir Sadov <vsadov@microsoft.com>
Tue, 25 May 2021 20:56:45 +0000 (13:56 -0700)
committerGitHub <noreply@github.com>
Tue, 25 May 2021 20:56:45 +0000 (20:56 +0000)
* singlefile support for universal binaries

* Update src/native/corehost/bundle/reader.h

Co-authored-by: Vitek Karas <vitek.karas@microsoft.com>
* tests for OSX universal apps

* Added a comment about testing strategy

Co-authored-by: Vitek Karas <vitek.karas@microsoft.com>
src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundleAndRun.cs
src/native/corehost/bundle/info.cpp
src/native/corehost/bundle/info.h
src/native/corehost/bundle/reader.cpp
src/native/corehost/bundle/reader.h
src/native/corehost/bundle/runner.cpp

index d717e0d..2c2d79b 100644 (file)
@@ -46,6 +46,24 @@ namespace Microsoft.NET.HostModel.Tests
                 .Pass();
         }
 
+        private string MakeUniversalBinary(string path, string rid)
+        {
+            string fatApp = path + ".fat";
+            string arch = BundleHelper.GetTargetArch(rid) == Architecture.Arm64 ? "arm64" : "x86_64";
+
+            // We will create a universal binary with just one arch slice and run it.
+            // It is enough for testing purposes. The code that finds the releavant slice
+            // would work the same regardless if there is 1, 2, 3 or more slices.
+            Command.Create("lipo", $"-create -arch {arch} {path} -output {fatApp}")
+                .CaptureStdErr()
+                .CaptureStdOut()
+                .Execute()
+                .Should()
+                .Pass();
+
+            return fatApp;
+        }
+
         private void BundleRun(TestProjectFixture fixture, string publishPath)
         {
             var hostName = BundleHelper.GetHostName(fixture);
@@ -65,6 +83,14 @@ namespace Microsoft.NET.HostModel.Tests
 
             // Run the extracted app
             RunTheApp(singleFile);
+
+            if (targetOS == OSPlatform.OSX)
+            {
+                string fatApp = MakeUniversalBinary(singleFile, fixture.CurrentRid);
+
+                // Run the fat app
+                RunTheApp(fatApp);
+            }
         }
 
         private string RelativePath(string path)
index 5f9f0da..4d58a00 100644 (file)
@@ -64,6 +64,7 @@ StatusCode info_t::process_header()
         const char* addr = map_bundle();
 
         reader_t reader(addr, m_bundle_size, m_header_offset);
+        m_offset_in_file = reader.offset_in_file();
 
         m_header = header_t::read(reader);
         m_deps_json.set_location(&m_header.deps_json_location());
@@ -116,13 +117,15 @@ char* info_t::config_t::map(const pal::string_t& path, const location_t* &locati
 
     trace::info(_X("Mapped bundle for [%s]"), path.c_str());
 
-    return addr + location->offset;
+    return addr + location->offset + app->m_offset_in_file;
 }
 
 void info_t::config_t::unmap(const char* addr, const location_t* location)
 {
     // Adjust to the beginning of the bundle.
-    addr -= location->offset;
+    const bundle::info_t* app = bundle::info_t::the_app;
+    addr -= location->offset - app->m_offset_in_file;
+
     bundle::info_t::the_app->unmap_bundle(addr);
 }
 
index 6252b5b..097f43c 100644 (file)
@@ -79,6 +79,7 @@ namespace bundle
         pal::string_t m_base_path;
         size_t m_bundle_size;
         int64_t m_header_offset;
+        int64_t m_offset_in_file;
         header_t m_header;
         config_t m_deps_json;
         config_t m_runtimeconfig_json;
index cff900d..a7b5e58 100644 (file)
@@ -32,7 +32,7 @@ void reader_t::set_offset(int64_t offset)
         throw StatusCode::BundleExtractionFailure;
     }
 
-    m_ptr = m_base_ptr + offset;
+    m_ptr = m_base_ptr + offset + m_offset_in_file;
 }
 
 void reader_t::bounds_check(int64_t len)
index 0ad5a26..576ecd0 100644 (file)
@@ -8,6 +8,39 @@
 #include "pal.h"
 #include "utils.h"
 
+// support for parsing OSX universal binary headers
+#ifdef TARGET_OSX
+#include "error_codes.h"
+#include <libkern/OSByteOrder.h>
+#include <mach-o/fat.h>
+#include <mach/machine.h>
+
+#if defined(TARGET_ARM64)
+#define TARGET_CPU_TYPE CPU_TYPE_ARM64
+#else
+#define TARGET_CPU_TYPE CPU_TYPE_X86_64
+#endif
+
+template <typename fat_arch_type>
+void* offset_in_FAT_universal_binary(const char* addr)
+{
+    uint32_t nfat_arch = OSSwapBigToHostInt32(((uint32_t*)addr)[1]);
+
+    fat_arch_type* arch_list = (fat_arch_type*)(addr + sizeof(uint32_t) * 2);
+    for (int i = 0; i < nfat_arch; i++)
+    {
+        if (OSSwapBigToHostInt32((uint32_t)arch_list[i].cputype) == TARGET_CPU_TYPE)
+        {
+            return &arch_list[i].offset;
+        }
+    }
+
+    trace::error(_X("Couldn't find offset in an universal fat binary."));
+    throw StatusCode::BundleExtractionFailure;
+}
+#endif // TARGET_OSX
+
+
 namespace bundle
 {
     // Helper class for reading sequentially from the memory-mapped bundle file.
@@ -19,6 +52,23 @@ namespace bundle
             , m_bound(bound)
             , m_bound_ptr(add_without_overflow(base_ptr, bound))
         {
+            m_offset_in_file = 0;
+
+#ifdef TARGET_OSX
+            // check for universal binary container and adjust the offset accordingly
+            uint32_t magic = OSSwapBigToHostInt32(((uint32_t*)base_ptr)[0]);
+            if (magic == FAT_MAGIC)
+            {
+                m_offset_in_file = OSSwapBigToHostInt32(*(uint32_t*)offset_in_FAT_universal_binary<fat_arch>(base_ptr));
+                trace::info(_X("FAT container detected. Offset in file:[%lx]"), m_offset_in_file);
+            }
+            else if (magic == FAT_MAGIC_64)
+            {
+                m_offset_in_file = OSSwapBigToHostInt64(*(uint64_t*)offset_in_FAT_universal_binary<fat_arch_64>(base_ptr));
+                trace::info(_X("FAT64 container detected. Offset in file:[%lx]"), m_offset_in_file);
+            }
+#endif
+
             set_offset(start_offset);
         }
 
@@ -37,6 +87,11 @@ namespace bundle
             return *m_ptr++;
         }
 
+        int64_t offset_in_file()
+        {
+            return m_offset_in_file;
+        }
+
         // Copy len bytes from m_ptr to dest
         void read(void* dest, int64_t len)
         {
@@ -67,6 +122,8 @@ namespace bundle
         const char* m_ptr;
         const int64_t m_bound;
         const char* const m_bound_ptr;
+
+        int64_t m_offset_in_file;
     };
 }
 
index 449f693..109a0f4 100644 (file)
@@ -21,6 +21,7 @@ StatusCode runner_t::extract()
 
         // Set the Reader at header_offset
         reader_t reader(addr, m_bundle_size, m_header_offset);
+        m_offset_in_file = reader.offset_in_file();
 
         // Read the bundle header
         m_header = header_t::read(reader);
@@ -74,7 +75,7 @@ bool runner_t::probe(const pal::string_t& relative_path, int64_t* offset, int64_
     assert(!entry->is_disabled());
     assert(entry->offset() != 0);
 
-    *offset = entry->offset();
+    *offset = entry->offset() + m_offset_in_file;
     *size = entry->size();
     *compressedSize = entry->compressedSize();