macOS上使用链接器的正确姿势

问题

对于任意汇编文件,在macOS上直接使用

as test.s -o test.o
ld test.o -lSystem -o test

会报错:

ld: library not found for -lSystem

解决方案

SDKROOT=`/usr/bin/xcrun --show-sdk-path -sdk macosx`
as test.s -o test.o
ld test.o -lSystem -L $(SDKROOT)/usr/lib -o test

分析

首先,由于在macOS上不能创建静态链接的可执行文件,因此在链接时必须使用-lSystem动态链接上系统库。

与此同时,在最近几个macOS版本中,Apple使用.tbd文件来减小无效系统库在系统中的空间,因此/usr/lib内不再有显式的libSystem.dylib文件。

为了让链接器能够正确找到系统库对应的.tbd文件,我们需要给链接器一个正确的路径。目前,大部分网络上的答案都是直接使用

ld test.o -lSystem -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib -o test

来显式指定路径,这种使用绝对路径的方法看上去就非常难受(虽然目前不存在portable的问题)。

经过大量的源码翻阅,LLVM的这则revision给出了正确的方法:使用xcrun --show-sdk-path获得当前SDK的路径。

虽然LLVM有Apple的背景在,但还是会担心这则revision到底是不是官方推荐的方案。

没办法,找了几个主流的开源编程语言的实现,发现确实他们都是这样做的。

Clang

首先是LLVM本身。代码位于compiler-rt/cmake/Modules/CompilerRTDarwinUtils.cmake文件(也有可能不在这个文件里,反正同样的代码结构出现在了许多文件中):

# On OS X SDKs can be installed anywhere on the base system and xcode-select can
# set the default Xcode to use. This function finds the SDKs that are present in
# the current Xcode.
function(find_darwin_sdk_dir var sdk_name)
  set(DARWIN_${sdk_name}_CACHED_SYSROOT "" CACHE STRING "Darwin SDK path for SDK ${sdk_name}.")
  set(DARWIN_PREFER_PUBLIC_SDK OFF CACHE BOOL "Prefer Darwin public SDK, even when an internal SDK is present.")

  if(DARWIN_${sdk_name}_CACHED_SYSROOT)
    set(${var} ${DARWIN_${sdk_name}_CACHED_SYSROOT} PARENT_SCOPE)
    return()
  endif()
  if(NOT DARWIN_PREFER_PUBLIC_SDK)
    # Let's first try the internal SDK, otherwise use the public SDK.
    execute_process(
      COMMAND xcrun --sdk ${sdk_name}.internal --show-sdk-path
      RESULT_VARIABLE result_process
      OUTPUT_VARIABLE var_internal
      OUTPUT_STRIP_TRAILING_WHITESPACE
      ERROR_FILE /dev/null
    )
  endif()
  if((NOT result_process EQUAL 0) OR "" STREQUAL "${var_internal}")
    execute_process(
      COMMAND xcrun --sdk ${sdk_name} --show-sdk-path
      RESULT_VARIABLE result_process
      OUTPUT_VARIABLE var_internal
      OUTPUT_STRIP_TRAILING_WHITESPACE
      ERROR_FILE /dev/null
    )
  else()
    set(${var}_INTERNAL ${var_internal} PARENT_SCOPE)
  endif()
  if(result_process EQUAL 0)
    set(${var} ${var_internal} PARENT_SCOPE)
  endif()
  message(STATUS "Checking DARWIN_${sdk_name}_SYSROOT - '${var_internal}'")
  set(DARWIN_${sdk_name}_CACHED_SYSROOT ${var_internal} CACHE STRING "Darwin SDK path for SDK ${sdk_name}." FORCE)
endfunction()

Rust

代码位于compiler/rustc_codegen_ssa/src/back/link.rs

fn get_apple_sdk_root(sdk_name: &str) -> Result<String, String> {
    // Following what clang does
    // (https://github.com/llvm/llvm-project/blob/
    // 296a80102a9b72c3eda80558fb78a3ed8849b341/clang/lib/Driver/ToolChains/Darwin.cpp#L1661-L1678)
    // to allow the SDK path to be set. (For clang, xcrun sets
    // SDKROOT; for rustc, the user or build system can set it, or we
    // can fall back to checking for xcrun on PATH.)
    // ...
    let res =
        Command::new("xcrun").arg("--show-sdk-path").arg("-sdk").arg(sdk_name).output().and_then(
            |output| {
                if output.status.success() {
                    Ok(String::from_utf8(output.stdout).unwrap())
                } else {
                    let error = String::from_utf8(output.stderr);
                    let error = format!("process exit with error: {}", error.unwrap());
                    Err(io::Error::new(io::ErrorKind::Other, &error[..]))
                }
            },
        );

    match res {
        Ok(output) => Ok(output.trim().to_string()),
        Err(e) => Err(format!("failed to get {} SDK path: {}", sdk_name, e)),
    }
}

Swift

代码位于lib/Driver/Driver.cpp

if (const Arg *A = Args.getLastArg(options::OPT_sdk)) {
  OI.SDKPath = A->getValue();
} else if (const char *SDKROOT = getenv("SDKROOT")) {
  OI.SDKPath = SDKROOT;
} else if (OI.CompilerMode == OutputInfo::Mode::Immediate ||
            OI.CompilerMode == OutputInfo::Mode::REPL) {
  if (TC.getTriple().isMacOSX()) {
    // In immediate modes, use the SDK provided by xcrun.
    // This will prefer the SDK alongside the Swift found by "xcrun swift".
    // We don't do this in compilation modes because defaulting to the
    // latest SDK may not be intended.
    auto xcrunPath = llvm::sys::findProgramByName("xcrun");
    if (!xcrunPath.getError()) {
      const char *args[] = {
        "--show-sdk-path", "--sdk", "macosx", nullptr
      };
      sys::TaskQueue queue;
      queue.addTask(xcrunPath->c_str(), args);
      queue.execute(nullptr,
                    [&OI](sys::ProcessId PID, int returnCode,
                          StringRef output, StringRef errors,
                          sys::TaskProcessInformation ProcInfo,
                          void *unused) -> sys::TaskFinishedResponse {
        if (returnCode == 0) {
          output = output.rtrim();
          auto lastLineStart = output.find_last_of("\n\r");
          if (lastLineStart != StringRef::npos)
            output = output.substr(lastLineStart+1);
          if (output.empty())
            OI.SDKPath = "/";
          else
            OI.SDKPath = output.str();
        }
        return sys::TaskFinishedResponse::ContinueExecution;
      });
    }
  }
}

总结

目前的主流编程语言,大部分都是通过xcrun的方式来确定链接器的相应的目录的。因此,使用这种方式是一种更为保险、更为通用的在macOS上使用链接器的正确姿势(即使后续的macOS更新不兼容这种方法,也可以查看这些编程语言相应的位置来找到更新的方案)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值