關於C語言, GCC/MSVC中,如何寫出一個真正意義上的不依賴庫的程序? int main() { return 0; } 上面這段代碼其實也是要依賴庫的哈。
關於C語言, GCC/MSVC中,如何寫出一個真正意義上的不依賴庫的程序?
int main() { return 0; }
上面這段代碼其實也是要依賴庫的哈。
要是鏈接的問題。編譯器頂多是配合一下鏈接器。也不是語言本身的問題。
只要你小心翼翼沒有真的用依賴庫里東西,你總能把程序鏈接為不依賴那些庫的樣子。否則,就是工具鏈的缺陷了。
MSVC 家族的 linker(LINK.EXE)除了 /NODEFAULTLIB 選項外,還能根據程序運行的環境(MSVC 里叫子系統),通過 /SUBSYSTEM 選項下達必要的指示。LINK.EXE 支持不同運行環境,除了大家常用的的 CONSOLE(命令行界面,例子:nslookup、xcopy)和 WINDOWS(圖形界面程序,例子:記事本、MS Word),還有:
和操作系統完全不沾邊也行:
編程語言也不是限定了是 C/C++。C# 也行的。在遷移工具鏈、庫到新平台等情況下可能發生依賴尚未實現的情況。解決起來都是類似套路。
具體到沒有語言 runtime 的情況,一般是先搞個 dummy type system(Platfrom.cs)。大概就是這種畫風。空著問題也不大的才能叫 intrinsic 嘛。
// https://github.com/dotnet/runtime/blob/master/src/coreclr/src/tools/aot/ILCompiler.TypeSystem.ReadyToRun.Tests/CoreTestAssembly/Platform.cs // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license.
#pragma warning disable 649 #pragma warning disable 169
namespace System { // Dummy core types to allow us compiling this assembly as a core library so that the type // system tests dont have a dependency on a real core library.
public class Object { internal IntPtr m_pEEType;
public virtual bool Equals(object other) { return false; }
public virtual int GetHashCode() { return 0; }
public virtual string ToString() { return null; }
~Object() { } }
public struct Void { } public struct Boolean { } public struct Char { } public struct SByte { } public struct Byte { } public struct Int16 { } public struct UInt16 { } public struct Int32 { } public struct UInt32 { } public struct Int64 { } public struct UInt64 { } public struct IntPtr { } public struct UIntPtr { } public struct Single { } public struct Double { } public abstract class ValueType { } public abstract class Enum : ValueType { } public struct Nullable& where T : struct { }
public sealed class String { } public abstract class Array : System.Collections.IList { } public abstract class Delegate { } public abstract class MulticastDelegate : Delegate { }
public struct RuntimeTypeHandle { } public struct RuntimeMethodHandle { } public struct RuntimeFieldHandle { }
public class Attribute { }
public class ThreadStaticAttribute : Attribute { }
public class Array& : Array, System.Collections.Generic.IList& { }
public class Exception { }
public ref struct TypedReference { private readonly ByReference& _value; private readonly RuntimeTypeHandle _typeHandle; }
public ref struct ByReference& { } }
namespace System.Collections { interface IEnumerable { }
interface ICollection : IEnumerable { }
interface IList : ICollection { } }
namespace System.Collections.Generic { interface IEnumerable& { }
interface ICollection& : IEnumerable& { }
interface IList& : ICollection& { } }
namespace System.Runtime.InteropServices { public enum LayoutKind { Sequential = 0, // 0x00000008, Explicit = 2, // 0x00000010, Auto = 3, // 0x00000000, }
public sealed class StructLayoutAttribute : Attribute { internal LayoutKind _val;
public StructLayoutAttribute(LayoutKind layoutKind) { _val = layoutKind; }
public LayoutKind Value { get { return _val; } } public int Pack; public int Size; }
public sealed class FieldOffsetAttribute : Attribute { private int _val; public FieldOffsetAttribute(int offset) { _val = offset; } public int Value { get { return _val; } } } }
namespace System.Runtime.CompilerServices { public sealed class IsByRefLikeAttribute : Attribute { } }
大多數簡單定義的類型可以直接從 runtime 的源碼中導入(複製粘貼即可)。注意遵守 MIT 協議。複雜或是涉及未實現的依賴,則只留個聲明,定義留空(ref 分支下的源)。你可以根據需要增刪類型和成員。
接下來是平台或運行環境庫。這裡我們準備一個 Uefi.cs 。因為 C 常常是事實上的 IDL,再不濟也大多能碰上 C/C++ 打頭陣。對於 C 家族的 C# 來說,這是相對容易的。
// (節選) // 用 using 引入別名 // . . . using UINT64 = System.UInt64; using INT64 = System.Int64; using UINT32 = System.UInt32; using INT32 = System.Int32; using UINT16 = System.UInt16; using CHAR16 = System.Char; using INT16 = System.Int16; using BOOLEAN = System.Boolean; using UINT8 = System.Byte; using CHAR8 = System.Byte; using INT8 = System.SByte; using UINTN = nuint; using INTN = nint; using GUID = System.Guid; using EFI_GUID = System.Guid; using System; using System.Runtime.InteropServices; using IN = System.Runtime.InteropServices.InAttribute; using OUT = System.Runtime.InteropServices.OutAttribute; using OPTIONAL = System.Runtime.InteropServices.OptionalAttribute; // . . . using static Uefi; using static EFI_STATUS; using static EFI_ALLOCATE_TYPE; using static EFI_KEY_SHIFT_STATE; using static EFI_KEY_TOGGLE_STATE; // . . . static unsafe partial class Uefi { public static readonly void* NULL; public const BOOLEAN TRUE = true; public const BOOLEAN FALSE = false; } // . . . unsafe struct IPv6_ADDRESS { fixed UINT8 Addr[16]; } [StructLayout(LayoutKind.Explicit)] unsafe struct EFI_IP_ADDRESS { [FieldOffset(0)] public fixed UINT32 Addr[4]; [FieldOffset(0)] public EFI_IPv4_ADDRESS v4; [FieldOffset(0)] public EFI_IPv6_ADDRESS v6; } // . . . // 也可用 wrapper 替代 typedef unsafe readonly struct EFI_HANDLE { readonly void* value__; EFI_HANDLE(void* value) =&> value__ = value; public static implicit operator void*(EFI_HANDLE value) =&> value.value__; public static implicit operator EFI_HANDLE(void* value) =&> new EFI_HANDLE(value); // . . . } // . . . // 為了更好的文檔支持,還可以對函數指針類型使用更複雜的 wrapper unsafe readonly struct EFI_INPUT_RESET { readonly delegate* unmanaged[Cdecl]& value__; EFI_INPUT_RESET(delegate* unmanaged[Cdecl]& value) =&> value__ = value; public static implicit operator delegate* unmanaged[Cdecl]&(EFI_INPUT_RESET value) =&> value.value__; public static implicit operator EFI_INPUT_RESET(delegate* unmanaged[Cdecl]& value) =&> new EFI_INPUT_RESET(value); // . . . /// & /// Reset the input device and optionally run diagnostics /// & /// &Protocol instance pointer.& /// & /// Driver may perform diagnostics on reset. /// & /// & /// &EFI_SUCCESS& /// The device was reset.& /// &EFI_DEVICE_ERROR& /// The device is not functioning properly and could not be reset.& /// & public EFI_STATUS Invoke( [IN] EFI_SIMPLE_TEXT_INPUT_PROTOCOL* This, [IN] BOOLEAN ExtendedVerification) =&> value__(This, ExtendedVerification); } /// & /// EFI Boot Services Table. /// & unsafe readonly partial struct EFI_BOOT_SERVICES { /// & /// The table header for the EFI Boot Services Table. /// & public readonly EFI_TABLE_HEADER Hdr; // . . . //EFI_CREATE_EVENT CreateEvent; public readonly EFI_CREATE_EVENT CreateEvent; //EFI_SET_TIMER SetTimer; public readonly EFI_SET_TIMER SetTimer; //EFI_WAIT_FOR_EVENT WaitForEvent; public readonly EFI_WAIT_FOR_EVENT WaitForEvent; // . . . // 還能用 void* 填補未有語言綁定的部分。 // EFI_EXIT_BOOT_SERVICES ExitBootServices; public readonly void* ExitBootServices; // . . . } // . . . /// & /// EFI System Table /// & unsafe readonly partial struct EFI_SYSTEM_TABLE { /// & /// The table header for the EFI System Table. /// & public readonly EFI_TABLE_HEADER Hdr; /// & /// A pointer to a null terminated string that identifies the vendor /// that produces the system firmware for the platform. /// & public readonly CHAR16* FirmwareVendor; /// & /// A firmware vendor specific value that identifies the revision /// of the system firmware for the platform. /// & public readonly UINT32 FirmwareRevision; /// & /// The handle for the active console input device. This handle must support /// EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. /// & public readonly EFI_HANDLE ConsoleInHandle; /// & /// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is /// associated with ConsoleInHandle. /// & public readonly EFI_SIMPLE_TEXT_INPUT_PROTOCOL* ConIn; /// & /// The handle for the active console output device. /// & public readonly EFI_HANDLE ConsoleOutHandle; /// & /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface /// that is associated with ConsoleOutHandle. /// & public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* ConOut; /// & /// The handle for the active standard error console device. /// This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. /// & public readonly EFI_HANDLE StandardErrorHandle; /// & /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface /// that is associated with StandardErrorHandle. /// & public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* StdErr; /// & /// A pointer to the EFI Runtime Services Table. /// & public readonly EFI_RUNTIME_SERVICES* RuntimeServices; /// & /// A pointer to the EFI Boot Services Table. /// & public readonly EFI_BOOT_SERVICES* BootServices; /// & /// The number of system configuration tables in the buffer ConfigurationTable. /// & public readonly UINTN NumberOfTableEntries; /// & /// A pointer to the system configuration tables. /// The number of entries in the table is NumberOfTableEntries. /// & public readonly EFI_CONFIGURATION_TABLE* ConfigurationTable; } // (非必須)根據需要 port 一些庫函數。 // . . . unsafe partial class BaseMemory { // 雖然有 boot service,但也能從 SDK 里抄來別的實現。 /// & /// Copies a source buffer to a destination buffer, and returns the destination buffer. /// This function copies Length bytes from SourceBuffer to DestinationBuffer, and returns /// DestinationBuffer. The implementation must be reentrant, and it must handle the case /// where SourceBuffer overlaps DestinationBuffer. /// If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT(). /// If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT(). /// & /// &The pointer to the destination buffer of the memory copy.& /// &The pointer to the source buffer of the memory copy.& /// &The number of bytes to copy from SourceBuffer to DestinationBuffer.& /// &DestinationBuffer& [System.Runtime.RuntimeExport(nameof(CopyMem))] public static void* CopyMem([OUT] void* DestinationBuffer, [IN]/*CONST*/ void* SourceBuffer, [IN] UINTN Length) { if (Length == 0) { return DestinationBuffer; } //ASSERT((Length - 1) &<= (MAX_ADDRESS - (UINTN)DestinationBuffer)); //ASSERT((Length - 1) &<= (MAX_ADDRESS - (UINTN)SourceBuffer)); if (DestinationBuffer == SourceBuffer) { return DestinationBuffer; } return InternalMemCopyMem(DestinationBuffer, SourceBuffer, Length); } /// & /// Copy Length bytes from Source to Destination. /// & /// &The target of the copy request.& /// &The place to copy from.& /// &The number of bytes to copy.& /// &Destination& static void* InternalMemCopyMem( [OUT] void* DestinationBuffer, [IN] /*CONST*/ void* SourceBuffer, [IN] UINTN Length) { // // Declare the local variables that actually move the data elements as // volatile to prevent the optimizer from replacing this function with // the intrinsic memcpy() // /*volatile*/ UINT8* Destination8; /*CONST*/ UINT8* Source8; /*volatile*/ UINT32* Destination32; /*CONST*/ UINT32* Source32; /*volatile*/ UINT64* Destination64; /*CONST*/ UINT64* Source64; UINTN Alignment; if ((((UINTN)DestinationBuffer 0x7) == 0) (((UINTN)SourceBuffer 0x7) == 0) (Length &>= 8)) { if (SourceBuffer &> DestinationBuffer) { Destination64 = (UINT64*)DestinationBuffer; Source64 = (/*CONST*/ UINT64*)SourceBuffer; while (Length &>= 8) { *(Destination64++) = *(Source64++); Length -= 8; } // Finish if there are still some bytes to copy Destination8 = (UINT8*)Destination64; Source8 = (/*CONST*/ UINT8*)Source64; while (Length-- != 0) { *(Destination8++) = *(Source8++); } } else if (SourceBuffer &< DestinationBuffer) { Destination64 = (UINT64*)((UINTN)DestinationBuffer + Length); Source64 = (/*CONST*/ UINT64*)((UINTN)SourceBuffer + Length); // Destination64 and Source64 were aligned on a 64-bit boundary // but if length is not a multiple of 8 bytes then they wont be // anymore. Alignment = Length 0x7; if (Alignment != 0) { Destination8 = (UINT8*)Destination64; Source8 = (/*CONST*/ UINT8*)Source64; while (Alignment-- != 0) { *(--Destination8) = *(--Source8); --Length; } Destination64 = (UINT64*)Destination8; Source64 = (/*CONST*/ UINT64*)Source8; } while (Length &> 0) { *(--Destination64) = *(--Source64); Length -= 8; } } } else if ((((UINTN)DestinationBuffer 0x3) == 0) (((UINTN)SourceBuffer 0x3) == 0) (Length &>= 4)) { if (SourceBuffer &> DestinationBuffer) { Destination32 = (UINT32*)DestinationBuffer; Source32 = (/*CONST*/ UINT32*)SourceBuffer; while (Length &>= 4) { *(Destination32++) = *(Source32++); Length -= 4; } // Finish if there are still some bytes to copy Destination8 = (UINT8*)Destination32; Source8 = (/*CONST*/ UINT8*)Source32; while (Length-- != 0) { *(Destination8++) = *(Source8++); } } else if (SourceBuffer &< DestinationBuffer) { Destination32 = (UINT32*)((UINTN)DestinationBuffer + Length); Source32 = (/*CONST*/ UINT32*)((UINTN)SourceBuffer + Length); // Destination32 and Source32 were aligned on a 32-bit boundary // but if length is not a multiple of 4 bytes then they wont be // anymore. Alignment = Length 0x3; if (Alignment != 0) { Destination8 = (UINT8*)Destination32; Source8 = (/*CONST*/ UINT8*)Source32; while (Alignment-- != 0) { *(--Destination8) = *(--Source8); --Length; } Destination32 = (UINT32*)Destination8; Source32 = (/*CONST*/ UINT32*)Source8; } while (Length &> 0) { *(--Destination32) = *(--Source32); Length -= 4; } } } else { if (SourceBuffer &> DestinationBuffer) { Destination8 = (UINT8*)DestinationBuffer; Source8 = (/*CONST*/ UINT8*)SourceBuffer; while (Length-- != 0) { *(Destination8++) = *(Source8++); } } else if (SourceBuffer &< DestinationBuffer) { Destination8 = (UINT8*)DestinationBuffer + (Length - 1); Source8 = (/*CONST*/ UINT8*)SourceBuffer + (Length - 1); while (Length-- != 0) { *(Destination8--) = *(Source8--); } } } return DestinationBuffer; } // . . . }
Protocol instance pointer.& /// &
/// Driver may perform diagnostics on reset. /// & /// & /// &EFI_SUCCESS& /// The device was reset.& /// &EFI_DEVICE_ERROR& /// The device is not functioning properly and could not be reset.& /// & public EFI_STATUS Invoke( [IN] EFI_SIMPLE_TEXT_INPUT_PROTOCOL* This, [IN] BOOLEAN ExtendedVerification) =&> value__(This, ExtendedVerification); } /// & /// EFI Boot Services Table. /// & unsafe readonly partial struct EFI_BOOT_SERVICES { /// & /// The table header for the EFI Boot Services Table. /// & public readonly EFI_TABLE_HEADER Hdr; // . . . //EFI_CREATE_EVENT CreateEvent; public readonly EFI_CREATE_EVENT CreateEvent; //EFI_SET_TIMER SetTimer; public readonly EFI_SET_TIMER SetTimer; //EFI_WAIT_FOR_EVENT WaitForEvent; public readonly EFI_WAIT_FOR_EVENT WaitForEvent; // . . . // 還能用 void* 填補未有語言綁定的部分。 // EFI_EXIT_BOOT_SERVICES ExitBootServices; public readonly void* ExitBootServices; // . . . } // . . . /// & /// EFI System Table /// & unsafe readonly partial struct EFI_SYSTEM_TABLE { /// & /// The table header for the EFI System Table. /// & public readonly EFI_TABLE_HEADER Hdr; /// & /// A pointer to a null terminated string that identifies the vendor /// that produces the system firmware for the platform. /// & public readonly CHAR16* FirmwareVendor; /// & /// A firmware vendor specific value that identifies the revision /// of the system firmware for the platform. /// & public readonly UINT32 FirmwareRevision; /// & /// The handle for the active console input device. This handle must support /// EFI_SIMPLE_TEXT_INPUT_PROTOCOL and EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL. /// & public readonly EFI_HANDLE ConsoleInHandle; /// & /// A pointer to the EFI_SIMPLE_TEXT_INPUT_PROTOCOL interface that is /// associated with ConsoleInHandle. /// & public readonly EFI_SIMPLE_TEXT_INPUT_PROTOCOL* ConIn; /// & /// The handle for the active console output device. /// & public readonly EFI_HANDLE ConsoleOutHandle; /// & /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface /// that is associated with ConsoleOutHandle. /// & public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* ConOut; /// & /// The handle for the active standard error console device. /// This handle must support the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL. /// & public readonly EFI_HANDLE StandardErrorHandle; /// & /// A pointer to the EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL interface /// that is associated with StandardErrorHandle. /// & public readonly EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL* StdErr; /// & /// A pointer to the EFI Runtime Services Table. /// & public readonly EFI_RUNTIME_SERVICES* RuntimeServices; /// & /// A pointer to the EFI Boot Services Table. /// & public readonly EFI_BOOT_SERVICES* BootServices; /// & /// The number of system configuration tables in the buffer ConfigurationTable. /// & public readonly UINTN NumberOfTableEntries; /// & /// A pointer to the system configuration tables. /// The number of entries in the table is NumberOfTableEntries. /// & public readonly EFI_CONFIGURATION_TABLE* ConfigurationTable; } // (非必須)根據需要 port 一些庫函數。 // . . . unsafe partial class BaseMemory { // 雖然有 boot service,但也能從 SDK 里抄來別的實現。 /// & /// Copies a source buffer to a destination buffer, and returns the destination buffer. /// This function copies Length bytes from SourceBuffer to DestinationBuffer, and returns /// DestinationBuffer. The implementation must be reentrant, and it must handle the case /// where SourceBuffer overlaps DestinationBuffer. /// If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT(). /// If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT(). /// & /// &
The pointer to the destination buffer of the memory copy.& /// &
The pointer to the source buffer of the memory copy.& /// &
The number of bytes to copy from SourceBuffer to DestinationBuffer.& /// &DestinationBuffer& [System.Runtime.RuntimeExport(nameof(CopyMem))] public static void* CopyMem([OUT] void* DestinationBuffer, [IN]/*CONST*/ void* SourceBuffer, [IN] UINTN Length) { if (Length == 0) { return DestinationBuffer; } //ASSERT((Length - 1) &<= (MAX_ADDRESS - (UINTN)DestinationBuffer)); //ASSERT((Length - 1) &<= (MAX_ADDRESS - (UINTN)SourceBuffer)); if (DestinationBuffer == SourceBuffer) { return DestinationBuffer; } return InternalMemCopyMem(DestinationBuffer, SourceBuffer, Length); } /// & /// Copy Length bytes from Source to Destination. /// & /// &
The target of the copy request.& /// &
The place to copy from.& /// &
The number of bytes to copy.& /// &Destination& static void* InternalMemCopyMem( [OUT] void* DestinationBuffer, [IN] /*CONST*/ void* SourceBuffer, [IN] UINTN Length) { // // Declare the local variables that actually move the data elements as // volatile to prevent the optimizer from replacing this function with // the intrinsic memcpy() // /*volatile*/ UINT8* Destination8; /*CONST*/ UINT8* Source8; /*volatile*/ UINT32* Destination32; /*CONST*/ UINT32* Source32; /*volatile*/ UINT64* Destination64; /*CONST*/ UINT64* Source64; UINTN Alignment;
if ((((UINTN)DestinationBuffer 0x7) == 0) (((UINTN)SourceBuffer 0x7) == 0) (Length &>= 8)) { if (SourceBuffer &> DestinationBuffer) { Destination64 = (UINT64*)DestinationBuffer; Source64 = (/*CONST*/ UINT64*)SourceBuffer; while (Length &>= 8) { *(Destination64++) = *(Source64++); Length -= 8; }
// Finish if there are still some bytes to copy Destination8 = (UINT8*)Destination64; Source8 = (/*CONST*/ UINT8*)Source64; while (Length-- != 0) { *(Destination8++) = *(Source8++); } } else if (SourceBuffer &< DestinationBuffer) { Destination64 = (UINT64*)((UINTN)DestinationBuffer + Length); Source64 = (/*CONST*/ UINT64*)((UINTN)SourceBuffer + Length); // Destination64 and Source64 were aligned on a 64-bit boundary // but if length is not a multiple of 8 bytes then they wont be // anymore. Alignment = Length 0x7; if (Alignment != 0) { Destination8 = (UINT8*)Destination64; Source8 = (/*CONST*/ UINT8*)Source64; while (Alignment-- != 0) { *(--Destination8) = *(--Source8); --Length; } Destination64 = (UINT64*)Destination8; Source64 = (/*CONST*/ UINT64*)Source8; } while (Length &> 0) { *(--Destination64) = *(--Source64); Length -= 8; } } } else if ((((UINTN)DestinationBuffer 0x3) == 0) (((UINTN)SourceBuffer 0x3) == 0) (Length &>= 4)) { if (SourceBuffer &> DestinationBuffer) { Destination32 = (UINT32*)DestinationBuffer; Source32 = (/*CONST*/ UINT32*)SourceBuffer; while (Length &>= 4) { *(Destination32++) = *(Source32++); Length -= 4; }
// Finish if there are still some bytes to copy Destination8 = (UINT8*)Destination32; Source8 = (/*CONST*/ UINT8*)Source32; while (Length-- != 0) { *(Destination8++) = *(Source8++); } } else if (SourceBuffer &< DestinationBuffer) { Destination32 = (UINT32*)((UINTN)DestinationBuffer + Length); Source32 = (/*CONST*/ UINT32*)((UINTN)SourceBuffer + Length); // Destination32 and Source32 were aligned on a 32-bit boundary // but if length is not a multiple of 4 bytes then they wont be // anymore. Alignment = Length 0x3; if (Alignment != 0) { Destination8 = (UINT8*)Destination32; Source8 = (/*CONST*/ UINT8*)Source32; while (Alignment-- != 0) { *(--Destination8) = *(--Source8); --Length; } Destination32 = (UINT32*)Destination8; Source32 = (/*CONST*/ UINT32*)Source8; } while (Length &> 0) { *(--Destination32) = *(--Source32); Length -= 4; } } } else { if (SourceBuffer &> DestinationBuffer) { Destination8 = (UINT8*)DestinationBuffer; Source8 = (/*CONST*/ UINT8*)SourceBuffer; while (Length-- != 0) { *(Destination8++) = *(Source8++); } } else if (SourceBuffer &< DestinationBuffer) { Destination8 = (UINT8*)DestinationBuffer + (Length - 1); Source8 = (/*CONST*/ UINT8*)SourceBuffer + (Length - 1); while (Length-- != 0) { *(Destination8--) = *(Source8--); } } } return DestinationBuffer; } // . . . }
還需要一些編譯器 contracts 或 runtime hacks(RuntimeHelpers.cs)。這一部分高度可變。常常要翻冷門資料。
// . . . namespace System.Runtime { internal sealed class RuntimeExportAttribute : Attribute { public RuntimeExportAttribute(string entry) { } } } namespace Internal.Runtime.CompilerHelpers { using System.Runtime; class StartupCodeHelpers { [RuntimeExport("RhpReversePInvoke2")] static void RhpReversePInvoke2() { } [RuntimeExport("RhpReversePInvokeReturn2")] static void RhpReversePInvokeReturn2() { } [RuntimeExport("__fail_fast")] static void FailFast() { for (; ; ) { } } [RuntimeExport("RhpPInvoke")] static void RhpPInvoke() { } [RuntimeExport("RhpPInvokeReturn")] static void RhpPInvokeReturn() { } } } // . . .
然後就可上 helloworld (Program.cs)了。
// 愚蠢的 C# 竟然沒有 #include // . . . using System; using System.Runtime.InteropServices; using IN = System.Runtime.InteropServices.InAttribute; using OUT = System.Runtime.InteropServices.OutAttribute; using OPTIONAL = System.Runtime.InteropServices.OptionalAttribute; using static BaseMemory; using static Uefi; using static EFI_STATUS; using static EFI_ALLOCATE_TYPE; using static EFI_KEY_SHIFT_STATE; using static EFI_KEY_TOGGLE_STATE; using static UefiEnvironment; // 目前僅捕獲 ImageHandle 和 SystemTable,沒別的用途。 // . . . unsafe class Program { static void Main() { } // 沒什麼用,讓 CSC 高興。 static EFI_STATUS LastStatus; static void SetLastStatus(EFI_STATUS value) =&> LastStatus = value; static EFI_STATUS GetLastStatus() =&> LastStatus; static void Initialize(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* SystemTable) { UefiEnvironment.Initialize(ImageHandle, SystemTable); } static void Exit(EFI_STATUS ExitStatus) { SystemTable-&>BootServices-&>Exit.Invoke(ImageHandle, ExitStatus, 0, null); } static void ExitIfError(EFI_STATUS Status) { if (EFI_ERROR(Status)) { Exit(Status); } } static void ExitIfError() =&> ExitIfError(LastStatus); static void WaitForKeyEvent() { EFI_STATUS Status; UINTN Index; if (EFI_ERROR(Status = SystemTable-&>BootServices-&>WaitForEvent.Invoke( 1, SystemTable-&>ConIn-&>WaitForKey, Index))) { SetLastStatus(Status); } } // . . . [System.Runtime.RuntimeExport(nameof(EfiMain))] static unsafe EFI_STATUS EfiMain( [IN] EFI_HANDLE ImageHandle, [IN] EFI_SYSTEM_TABLE* SystemTable) { Initialize(ImageHandle, SystemTable); EFI_STATUS Status; string hello = "Hello, world! "; fixed (CHAR16* p = hello) { if (EFI_ERROR(Status = SystemTable-&>ConOut-&>OutputString.Invoke( SystemTable-&>ConOut, p))) { return Status; } } string msg = "Press any key to exit this application . . . "; fixed (CHAR16* p = msg) { if (EFI_ERROR(Status = SystemTable-&>ConOut-&>OutputString.Invoke( SystemTable-&>ConOut, p))) { return Status; } } WaitForKeyEvent(); ExitIfError(); return EFI_SUCCESS; } }
好了,開始編譯吧。
csce.exe /debug:embedded /noconfig /nostdlib /runtimemetadataversion:v4.0.30319 *.cs /out:EFIApp.exe /langversion:latest /unsafe
/nostdlib 是關鍵,免得和 dummy type system 衝突。
再 AOT 編譯。首選當然是新版的 Microsoft.Dotnet.ILCompiler (有個同名的舊版相關項目,不要搞混了哦)。可以用 nuget 下載 win-x64 的 runtime 包(runtime.win-x64.microsoft.dotnet.ilcompiler,源目錄: https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json),裡面能找到 ilc.exe 。
ilc.exe EFIApp.exe -o EFIApp.obj --systemmodule EFIApp --map EFIApp.map -O
最後鏈接成可以裸機執行的 UEFI 程序。/subsystem:EFI_APPLICATION 指定了子系統。要用 x64 Native Tools。
link.exe /subsystem:EFI_APPLICATION EFIApp.obj /entry:EfiMain /incremental:no /out:EFIApp.EFI
用 Diskpart 或 DiscUtils 創建鏡像文件,載入到虛擬機里吧。我在鏡像里放入了一套 UEFI shell 環境,方便調用。
當然,這只是 helloworld。要是為了能寫出複雜的程序,還得把 StdLib 在新環境里實現一遍。
通常在你寫一個程序並使用gcc編譯時,gcc會自動將main函數作為__libc_start_main的一個函數指針來調用。而__libc_start_main是由_start函數調用的,_start函數也是Linux操作系統指定的程序入口點,這些工作都由gcc通過鏈接crt1.o提前幫你完成了,因此如果你想寫一個不依賴任何庫的程序,你需要自己實現_start函數。可以參考docker中提供的一個最小的鏡像hello-world,其中就有一段類似hello-world的C語言代碼。
__libc_start_main
_start
gcc
crt1.o
docker
#include &
const char message[] = " " DOCKER_GREETING " " "This message shows that your installation appears to be working correctly. " " " "To generate this message, Docker took the following steps: " " 1. The Docker client contacted the Docker daemon. " " 2. The Docker daemon pulled the "" DOCKER_IMAGE "" image from the Docker Hub. " " (" DOCKER_ARCH ") " " 3. The Docker daemon created a new container from that image which runs the " " executable that produces the output you are currently reading. " " 4. The Docker daemon streamed that output to the Docker client, which sent it " " to your terminal. " " " "To try something more ambitious, you can run an Ubuntu container with: " " $ docker run -it ubuntu bash " " " "Share images, automate workflows, and more with a free Docker ID: " " https://hub.docker.com/ " " " "For more examples and ideas, visit: " " https://docs.docker.com/get-started/ " " ";
void _start() { syscall(SYS_write, 1, message, sizeof(message) - 1); syscall(SYS_exit, 0); }
注意為了不依賴任何glibc的庫函數,所有的操作都需要通過系統調用。
參考資料
GCC示例。
選項-nostdlib -nostdinc -fno-builtin
視情況禁用stack protector.
手工ld鏈接目標文件,使用自定義的鏈接腳本。入口點需要和你的入口函數對應(但不需要是main())。至少要收集.text .data .bss三個段,必要的話注意對齊和lma/vma對應。棧怎麼初始化看你的意圖了,這裡先不作假定。
基本思路其實就是不鏈接libc(也不引入對接libc的代碼),以防萬一可以禁用builtin函數。在拋棄這些的情況下,預設的鏈接腳本已經沒法工作(連原先約定好的入口點都不存在了),所以需要定製一下。這樣下來,OS自帶的私貨也可以屏蔽掉。
布局和邏輯編寫處理得當的話,這種程序可以在用戶態下運行。畢竟鏈接器正常輸出了可執行映像,基礎還是有的。
GCC 的話,你沒法擺脫 libgcc,就算你寫沒有任何外部庫和系統調用的純 C 代碼,也會觸發到 libgcc 的調用,因為並非所有 C 語言代碼都可以一比一完全映射成裸彙編,
比如:
有些平台沒有整數除法指令(或者連整數乘法指令都沒有),那麼你寫個 x * y ,或者 x / y 的整數運算,在代碼生成階段都可能會調用到 libgcc 裡面的模擬乘除法實現。
比如 32 位平台進行 64 位的整數運算,加減運算,編譯器可能會生成 inline 的彙編指令,但是乘除法代碼量比較多,本身相對複雜,也許會生成一個函數調用,靠 libgcc 的軟體乘除法來模擬。如果你想在 16 位平台下進行 32 位整數運算也有類似問題。
還有沒有浮點數運算單元的平台,都需要靠 libgcc 來提供模擬,你程序里沒用浮點還好,用了的話可能就會有 libgcc 依賴。
舉個例子,x86 下你的程序使用了 64 位整數運算:
int64_t foo(int64_t x, int64_t y) { int64_t z = x * y; int64_t w = x / y; return (z + w); }
如果是 32 位下面 -O2 編譯,生成的代碼就是:
movl 48(%esp), %ecx movl 56(%esp), %ebx movl 52(%esp), %ebp movl 60(%esp), %eax movl %ebx, 8(%esp) imull %ecx, %eax movl %ecx, (%esp) imull %ebx, %ebp addl %eax, %ebp movl %ecx, %eax mull %ebx movl %eax, %esi movl 60(%esp), %eax movl %edx, %edi addl %ebp, %edi movl %eax, 12(%esp) movl 52(%esp), %eax movl %eax, 4(%esp) call ___divdi3
你會發現 64 位乘法被展開成兩次 32 位乘法和加法操作了,因為乘法實現代碼量比較短,gcc 選擇直接 inline 指令展開,而後面除法相對複雜,gcc 覺得每個整數除法都 inline 展開的話,太虧了,弄個函數調用吧,所以你看到後面有個 __divdi3 的調用,就是做這個事情的。
你如果編譯時加了 「-nostdlib -fno-builtin -nodefaultlibs」就會報錯,說找不到 __divdi3 函數,
那麼我是不是可以通過避免在32位平台下使用 64 位整數來去掉 libgcc 的依賴呢?
其實並不能,你不知道 gcc 到底將哪些操作弄成了 inline 指令展開,哪些操作弄成了 libgcc 函數調用,傳統 arm 下沒有整數除法,risc v 標準指令連乘法都沒有,很多嵌入式平台沒有硬浮點支持。不光是數學運算,還有異常處理,棧操作,都有可能觸發 libgcc 調用。
歸根結底是你完全沒有辦法猜測 gcc 生成代碼時哪些地方調用了 libgcc,哪些地方直接 inline 指令展開,目標平台不一樣,編譯參數不一樣,結果都不相同。
所以你需要記住的是 libgcc 是 gcc 的一部分,即便你固定了目標平台就是 x86,編譯參數就是這幾個,代碼里繞著圈子走,盡量不觸碰需要 libgcc 調用的寫法,需要模擬時自己模擬,你以為萬事大吉?
不是,當你升級 gcc 版本時,你就發現全部都掛了。
所以不管你寫嵌入式代碼,操作系統實驗,或者普通應用程序,你可以挑戰 libc,把它的依賴幹掉,但是別去挑戰 libgcc,它是 gcc 的一部分。
操作系統的IO操作基本都是以系統調用的方式提供,一般的libc會封裝好這些操作,假如不鏈接libc的話就需要自己用彙編的方式使用系統調用了,而且c語言提供了asm關鍵字來實現彙編內聯,用彙編實現系統調用和libc里提供的syscall函數的使用方法大同小異。
我不太會寫彙編,先瞎寫一下,如果有錯誤或者不規範的地方希望各位指證出來。
char msg[] = "hello,world! "; int write(int fd, void* buf, unsigned int count) { register int res __asm__("rax"); asm volatile( "mov $1,%%rax;" "syscall;" :); return res; } static inline void exit(int status) { asm volatile( "mov $60,%%rax;" "syscall" :); } void _start() { write(1, msg, sizeof(msg)); exit(0); }
然後用gcc xxx.c -nostdlib -nostdinc -nostartfiles -fno-builtin -fno-stack-protector編譯
※Windows環境下的安裝gcc※[技術論壇]6月22日HelloLLVM上海線下聚會
TAG:操作系統 | C編程語言 | CC | GCC | C標準庫 |