Symbol stripping on OSX. Any luck?

I’ve been trying to learn how to strip symbols from plugins. It was pointed out to me that the linker-based flags in XCode don’t really have much effect. I started out with Jules’ suggestion (“strip -x -S YourPlugin.vst/Contents/MacOS/YourPlugin”) and have been digging through man pages. No matter what I do, nm still shows hundreds of internal symbols. The majority of these have no dependencies outside of the plugin, so I don’t see why they’d be of any interest to the host executable. They would be of some interest to those whose intents aren’t purely musical.

Perhaps there’s also something I need to do in the linker, but I must admit to being stumped. I wonder if there’s anyone who might point me toward the proper path.

are you building with -fvisibility=hidden , and possibly also -fvisibility-inlines-hidden ? that’s what I’m using, stripping with ‘strip -x’ and no unwanted symbol is visible in the plugin with nm

Ah. Didn’t even think of going at the compiler flags. That’s a [size=150]huge[/size] help. Thanks!

I use the following method:

Export only the needed symbols by describing them in a .exp file, and passing “-exported_symbols_list path/to/file.exp” to the linker.

The file would contain, for example:

# -----------------------------------------------------------------------


# -----------------------------------------------------------------------
# AudioUnit
# These two symbols must match the following syntax:
# Main entry: _$(JucePlugin_AUExportPrefix)Entry
# View entry: _$(JucePlugin_AUExportPrefix)ViewEntry
# JucePlugin_AUExportPrefix is defined in JucePluginCharacteristics.h


# -----------------------------------------------------------------------


Note that you need to specify different files for i386 and x86_64 symbols, as RTAS and AudioUnit’s view are exported only in i386.

Then I’m using the following perl script, found on a Apple mailing list thread (link is dead now), to strip down the rest:

# /usr/bin/perl -w
# Last updated: 21MAR06, Andy O'Meara and Rob Barris
# This is an Xcode post-build phase script for devs who sleep better at night knowing
#    that their deployment binaries are as stripped as possible.  This makes life more difficult
#    for a hacker/cracker to locate sensitive code to trace, study, and/or extract.
# This script will execute only if the Xcode "Deployment Postprocessing" setting
# The downside to shipping a stripped binary is that your user's crash reports
#    will be useless unless you have a link map to convert code offsets (from a stack trace)
#    into proc names.  To address this, this script moves your pre-stripped executable
#    to the build dir, appending "_full" to the filename, allowing you to retain it for
#    the day you need it in order to decipher a stack trace.  You do this by using 'atos'
#    with the original generated binary (type 'man atos' for info).
# Recommended Xcode build settings:
#    Dead Code Stripping                  YES
#    Only Link In Essential Symbols       NO
#    Deployment Postprocessing            YES (this activates this script)
#    Strip Linked Product                 NO
#    Use Separate Strip                   NO
#    Strip Style                          All Symbols
#    Strip Debug Symbols During Copy      NO
#    Preserve Private External Symbols    NO
#    Separate PCH Symbols                 YES
#    Symbols Hidden By Default            YES (Critical!)
#    Inline Functions Hidden              YES
# Note that if you're building a dynamic library, you'll need to explicitly
#    declare any symbols that you want to be exported.  See the following:
#    file:///Developer/ADC%20Reference%20Library/documentation/DeveloperTools/Conceptual/CppRuntimeEnv/Articles/SymbolVisibility.html

use strict;

die "$0: Must be run from Xcode" unless $ENV{"BUILT_PRODUCTS_DIR"};

# This script is activated via an Xcode env flag.
   exit 0;

# Strip output only for Protect configuration
if ( $ENV{CONFIGURATION} ne "Protect" ) {
   exit 0;

print "\n\n==================== Commencing external stripping phase...\n";
my $BINARY_i386  = "${BINARY}_i386";
my $BINARY_x86_64   = "${BINARY}_x86_64";

# Extract each arch into a "thin" binary for stripping
`lipo "$BINARY" -thin x86_64 -output "$BINARY_x86_64" `;
`lipo "$BINARY" -thin i386   -output "$BINARY_i386"`;

# Retain the orignal binary for QA and use with the util 'atos'
`mv -f "$BINARY" "$BINARY_FULL"`;

# Perform desired stripping on each thin binary.  
`strip -S -x -o "${BINARY_i386}_tmp"   -r "$BINARY_i386"`;
`strip -S -x -o "${BINARY_x86_64}_tmp" -r "$BINARY_x86_64" `;

# We're now done with the original thin binaries, so chuck them.
`rm -f "$BINARY_i386"`;
`rm -f "$BINARY_x86_64" `;

# Make the new universal binary from our stripped thin pieces.
`lipo -arch i386 "${BINARY_i386}_tmp" -arch x86_64 "${BINARY_x86_64}_tmp" -create -output "$BINARY"`;

# We're now done with the temp thin binaries, so chuck them.
`rm -f "${BINARY_i386}_tmp"`;
`rm -f "${BINARY_x86_64}_tmp" `;
`rm -f "${BINARY_FULL}" `;

print "\n==================== External strip phase complete\n";


I do separate strips in Terminal, after I build in Xcode. None of the build-based strips I tried worked worth a darn.

Sean Costello

I’ve found that the suggestion by [color=#0040FF]jpo[/color] (second post on this thread), followed by a terminal-style ‘strip’ command has been effective. I placed the strip command into an existing Xcode build phase. So the whole thing is still a one-step operation from the human point of view.

Hi, Could you also state WHERE should this script be inserted? as a separate Xcode build-phase (script build-phase)? How does Xcode know that this is the “post-processing” phase? by name? by the order in build-phases? Some info is missing.

In addition - The side-bar help of Xcode for “Deployment PostProcessing” states:

“Activating this setting indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values. [DEPLOYMENT_POSTPROCESSING]”

So - at least in Xcode 7.3 - maybe this script is not needed (provided that other settings are correctly set) ?

Last, I tried EVERYTHING in this thread, including terminal-level ‘strip’ command - but my stubborn binaries still reveal everything when nm -a is applied on them.

I’m stumped.

I am currently stripping all my projects with an external “strip -x” invoked in a post-build script, and it works flawlessly (I mean, some symbols are still listed with “nm -a” but that’s because they are global symbols intended to be exported)

Silly question: when invoking the strip command, you must enter the path of the actual binary inside the resulting bundle. You are actually doing that, right?

Well, I finally succeeded with it, stripping anything to my heart’s content, and letting Xcode do everything without any post-processing or extra scripting. I had several problems, that I will hereby describe, each capable of ruining the stripping. Let others beware of these pitfalls. Generally, I have a C++ based project (some OS-X Daemon) depending on several private frameworks (of our making too), a few dylib’s, one System-Preferences panel, and one Installer Extension plugin (the last two are cocoa code-bundles). All these are targets within a single Xcode project.

I also have quite a few script build-phases that combine this mess into beautiful single Code-bundle (a preferences panel) containing all the rest.

My first mistake - I didn’t turn on “symbols hidden by default”. This made all my symbols global - hence no stripping could ever remove them.

My second mistake - some of my private frameworks, are really wrappers, re-exporting sets of 3rd-party dylibs (boost, ahemmm). here I forgot to strip the original 3rd-party dylibs before re-exporting them. since all this is done in script build-phases, Xcode didn’t know about them, and so they couldn’t be stripped normally.

My third mistake: Xcode only applies stripping linked-product, if you turn on “deployment post-processing”. This is written in the help tab of the “strip linked product”, but almost impossible to see. I could not understand why Xcode was ignoring all my strip settings. So - you must turn post-processing on.

My fourth and most severe mistake - I misunderstood Xcode UI. Trying to be clever programmer, I usually keep as much of my build-settings at the Project level, in the hopes that targets will inherit these settings, and will so be aligned well (same compiler, language settings, etc.) The normal way to do that, is by simply deleting any setting at the target level- and when it is empty — Xcode usually inherits from upper level (project). This is NOT true… When no value exists for a setting, this means “default” and not “inherited” however - in most cases, but not all, Xcode “defaults” to inherited behavior.

In the case of strip style setting, EACH TARGET TYPE has its own default! so when I set the project level to “strip local symbols”, I was sure it will be applied to all my targets - but executables default to “strip all”, Code-bundles default to “strip local symbols” and shared libraries (like frameworks and dylibs) default to only “strip debug symbols”. The moment I manually set all my framework and dylib targets to strip local symbols —

it finally all worked fine. Stripping like a charm. My final product is now 2/3 its original size, and hackers will find it harder to decipher.

There is a recent option in the Projucer, Xcode project section, which does all of this automatically now, doesn’t it ?

1 Like