Skip to content

Instantly share code, notes, and snippets.

@Evian-Zhang
Created June 15, 2022 09:03
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Evian-Zhang/19c63a1f1a1a58bdd4b86836a8b3ba0f to your computer and use it in GitHub Desktop.
Save Evian-Zhang/19c63a1f1a1a58bdd4b86836a8b3ba0f to your computer and use it in GitHub Desktop.
Proper way to handle "ld: library not found for -lSystem" in macOS
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 上使用链接器的正确姿势

问题

对于任意汇编文件,在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更新不兼容这种方法,也可以查看这些编程语言相应的位置来找到更新的方案)。

@HeavenlyBard
Copy link

no,只需使用“clang helloworld.o -o helloworld”,请忘记ld和-lSystem。

@labspc
Copy link

labspc commented Mar 30, 2024

在使用C和汇编混编的时候,可能会出现ld 找不到的情况,可以采用下面的方式直接ok:

ld swap.o swap-a64.o -lSystem -L $(xcrun --show-sdk-path -sdk macosx)/usr/lib -o prog

总结:@Evian-Zhang ,说的有道理。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment