对于任意汇编文件,在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到底是不是官方推荐的方案。
没办法,找了几个主流的开源编程语言的实现,发现确实他们都是这样做的。
首先是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()
代码位于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)),
}
}
代码位于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更新不兼容这种方法,也可以查看这些编程语言相应的位置来找到更新的方案)。
no,只需使用“clang helloworld.o -o helloworld”,请忘记ld和-lSystem。