[fix] include OpenSSL resource symbols in iOS archives

This commit is contained in:
dijunkun
2026-11-01 03:11:18 +08:00
parent c2bfce03f2
commit 3597fd4b3e
5 changed files with 108 additions and 8 deletions
@@ -222,6 +222,7 @@
300000000000000000000001 /* Frameworks */,
600000000000000000000002 /* Resources */,
300000000000000000000002 /* Embed Frameworks */,
700000000000000000000002 /* Generate OpenSSL symbols */,
);
buildRules = (
);
@@ -318,6 +319,24 @@
shellPath = /bin/zsh;
shellScript = "set -e\npython3 \"${PROJECT_DIR}/scripts/prepare_source_metadata.py\" --output \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SourceMetadata.json\"\n\"${PROJECT_DIR}/scripts/build_minirtc_ios.sh\"\npython3 \"${PROJECT_DIR}/scripts/verify_native_build.py\" --inputs \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --xmake-version \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/xmake-version.txt\"\npython3 \"${PROJECT_DIR}/scripts/package_openssl_ios.py\" --inputs \"${PROJECT_DIR}/Vendor/${PLATFORM_NAME}/${CONFIGURATION}/libCrossDeskMiniRTC.a.inputs.sha256\" --output \"${BUILT_PRODUCTS_DIR}/OpenSSL.framework\" --platform \"${PLATFORM_NAME}\"\n";
};
700000000000000000000002 /* Generate OpenSSL symbols */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"$(TARGET_BUILD_DIR)/$(FRAMEWORKS_FOLDER_PATH)/OpenSSL.framework/OpenSSL",
"$(PROJECT_DIR)/scripts/generate_openssl_symbols.py",
);
name = "Generate OpenSSL symbols";
outputPaths = (
"$(DWARF_DSYM_FOLDER_PATH)/OpenSSL.framework.dSYM/Contents/Resources/DWARF/OpenSSL",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/zsh;
shellScript = "set -e\nif [[ \"${DEBUG_INFORMATION_FORMAT}\" == \"dwarf-with-dsym\" ]]; then\n python3 -B \"${PROJECT_DIR}/scripts/generate_openssl_symbols.py\" --binary \"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OpenSSL.framework/OpenSSL\" --output \"${DWARF_DSYM_FOLDER_PATH}/OpenSSL.framework.dSYM\"\nfi\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -496,7 +515,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
ENABLE_BITCODE = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -548,7 +567,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 3;
CURRENT_PROJECT_VERSION = 4;
ENABLE_BITCODE = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
FRAMEWORK_SEARCH_PATHS = (
+10
View File
@@ -586,6 +586,16 @@ Xcode removes the static archive from the embedded framework (it may insert a
small resource-framework stub) and preserves `PrivacyInfo.xcprivacy`, source
metadata and license texts. The application does not load a dynamic OpenSSL copy.
For builds using `DWARF with dSYM File`, the **Generate OpenSSL symbols** phase
runs after embedding. It verifies that Xcode's placeholder has no executable
code, generates `OpenSSL.framework.dSYM` from that exact binary, and checks its
UUIDs. This companion describes the empty resource stub, not the statically
linked OpenSSL implementation. Xcode includes it alongside the app's dSYM in
the archive so Organizer can upload symbols for every embedded Mach-O binary.
`verify_app_bundle.py` checks both UUID matches when given an archived app;
for an ordinary Release build, pass `--dsym-dir /path/to/Release-iphoneos`.
The CI ZIP includes both dSYM bundles.
The iOS-specific OpenSSL recipe disables POSIX metadata calls, automatic
`openssl.cnf` loading, and the unused file-URI store provider. It selects the
upstream `getrandom` entropy backend, which uses Darwin's `getentropy` on the
@@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Generate matching symbols for Xcode's embedded, codeless OpenSSL framework."""
import argparse
from pathlib import Path
import re
import subprocess
import sys
def uuids(path):
output = subprocess.check_output(["xcrun", "dwarfdump", "--uuid", str(path)], text=True)
result = dict((arch, uuid.upper()) for uuid, arch in
re.findall(r"UUID: ([0-9A-Fa-f-]{36}) \(([^)]+)\)", output))
if not result:
raise ValueError(f"No Mach-O UUIDs found: {path}")
return result
def verify_symbols(binary, dsym):
dwarf = dsym / "Contents/Resources/DWARF" / binary.name
if not dwarf.is_file():
raise ValueError(f"Missing dSYM DWARF file: {dwarf}")
if uuids(binary) != uuids(dwarf):
raise ValueError(f"dSYM UUIDs do not match {binary}: {dwarf}")
def generate(binary, output):
# This is only for Xcode's resource placeholder. Real OpenSSL code remains
# statically linked into the app and must not receive an empty dSYM here.
commands = subprocess.check_output(["xcrun", "otool", "-l", str(binary)], text=True)
text_sizes = re.findall(
r"sectname __text\s+segname __TEXT\s+addr \S+\s+size (0x[0-9a-fA-F]+)", commands)
if not text_sizes or any(int(size, 16) for size in text_sizes):
raise ValueError(f"Expected a codeless OpenSSL resource stub: {binary}")
output.parent.mkdir(parents=True, exist_ok=True)
# No function-level debug info exists in this empty stub. dsymutil still
# emits a valid UUID-matched DWARF companion; quiet suppresses that expected
# warning only after verifying that there is no executable code to lose.
subprocess.run(["xcrun", "dsymutil", "--quiet", str(binary), "-o", str(output)], check=True)
verify_symbols(binary, output)
subprocess.run(["xcrun", "dwarfdump", "--verify", str(output)], check=True)
print(f"Generated UUID-matched OpenSSL resource symbols: {output}")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--binary", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
args = parser.parse_args()
generate(args.binary, args.output)
if __name__ == "__main__":
try:
main()
except (OSError, ValueError, subprocess.CalledProcessError) as error:
print(f"OpenSSL symbols: {error}", file=sys.stderr)
sys.exit(1)
+13 -2
View File
@@ -10,8 +10,10 @@ import re
import subprocess
import sys
from generate_openssl_symbols import verify_symbols
def verify(app, require_release=False):
def verify(app, require_release=False, dsym_dir=None):
info = plistlib.loads((app / "Info.plist").read_bytes())
binary = app / info["CFBundleExecutable"]
subprocess.run(["xcrun", "lipo", str(binary), "-verify_arch", "arm64"], check=True)
@@ -63,6 +65,14 @@ def verify(app, require_release=False):
sdk_symbols = subprocess.check_output(["xcrun", "nm", "-gU", str(embedded_binary)], text=True)
if re.search(r"\b_(?:SSL_new|OPENSSL_init_crypto)\b", sdk_symbols):
raise ValueError("Unexpected dynamic OpenSSL implementation in resource framework")
# Archive validation includes the exact files that Organizer uploads.
if dsym_dir is None and app.parents[2].suffix == ".xcarchive":
dsym_dir = app.parents[2] / "dSYMs"
if dsym_dir is not None:
verify_symbols(binary, dsym_dir / f"{app.name}.dSYM")
if embedded_binary.exists():
verify_symbols(embedded_binary, dsym_dir / "OpenSSL.framework.dSYM")
print("Verified app and embedded OpenSSL dSYM UUIDs")
linked = subprocess.check_output(["xcrun", "otool", "-L", str(binary)], text=True)
if "OpenSSL.framework" in linked:
raise ValueError("OpenSSL must be statically linked into the application")
@@ -79,8 +89,9 @@ def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("app", type=Path)
parser.add_argument("--require-release", action="store_true")
parser.add_argument("--dsym-dir", type=Path)
args = parser.parse_args()
verify(args.app, args.require_release)
verify(args.app.resolve(), args.require_release, args.dsym_dir)
if __name__ == "__main__":