Chapter 24 - error: Multiple internal symbols found for 'context'

Following along, I made all the way to chapter 24, where I started getting the below error, now most lldb command I try for this chapter go bonkers.

Error - output from launch of scheme to errors:

  • thread #1, queue = ‘com.apple.main-thread’, stop reason = breakpoint 1.1
    • frame #0: 0x0000000107a41276 libsystem_c.dylibgetenv frame #1: 0x00000001023b2adb libobjc.A.dylibenviron_init + 462
      frame #2: 0x00000001023b5d9a libobjc.A.dylib_objc_init + 32 frame #3: 0x0000000107902ae6 libdispatch.dylib_os_object_init + 13
      frame #4: 0x0000000107904deb libdispatch.dyliblibdispatch_init + 297 frame #5: 0x0000000102cacaa2 libSystem.dyliblibSystem_initializer + 164
      frame #6: 0x0000000101aabc16 dyld_simImageLoaderMachO::doModInitFunctions(ImageLoader::LinkContext const&) + 420 frame #7: 0x0000000101aabe46 dyld_simImageLoaderMachO::doInitialization(ImageLoader::LinkContext const&) + 40
      frame #8: 0x0000000101aa76da dyld_simImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 330 frame #9: 0x0000000101aa766d dyld_simImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 221
      frame #10: 0x0000000101aa6898 dyld_simImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 134 frame #11: 0x0000000101aa692c dyld_simImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 74
      frame #12: 0x0000000101a9b135 dyld_simdyld::initializeMainExecutable() + 126 frame #13: 0x0000000101a9ec98 dyld_simdyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 4412
      frame #14: 0x0000000101a9a3d4 dyld_simstart_sim + 136 frame #15: 0x0000000104da5ded dylddyld::useSimulatorDyld(int, macho_header const*, char const*, int, char const**, char const**, char const**, unsigned long*, unsigned long*) + 2200
      frame #16: 0x0000000104da37a3 dylddyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 436 frame #17: 0x0000000104d9f3d4 dylddyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 453
      frame #18: 0x0000000104d9f1d2 dyld`_dyld_start + 54
      Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion.Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion.Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion.Warning: hit breakpoint while running function, skipping commands and conditions to prevent recursion.(lldb) search SwiftObject
      error:

error: error: error: expected ‘;’ at end of declaration
error: Multiple internal symbols found for ‘context’
id = {0x00000513}, range = [0x000000010a9558f8-0x000000010a955970), name=“context”, mangled=“_ZL7context”
id = {0x00000534}, range = [0x000000010a955de8-0x000000010a955e48), name=“context”

(lldb) search SwiftObject
error:


error: error: error: expected ‘;’ at end of declaration
error: Multiple internal symbols found for ‘context’
id = {0x00000513}, range = [0x000000010a9558f8-0x000000010a955970), name=“context”, mangled=“_ZL7context”
id = {0x00000534}, range = [0x000000010a955de8-0x000000010a955e48), name=“context”

(lldb) search RayView -b
error:


error: error: error: expected ‘;’ at end of declaration
error: Multiple internal symbols found for ‘context’
id = {0x00000513}, range = [0x000000010a9558f8-0x000000010a955970), name=“context”, mangled=“_ZL7context”
id = {0x00000534}, range = [0x000000010a955de8-0x000000010a955e48), name=“context”

(lldb)

@lolgrep Can you please help with this when you get a chance? Thank you - much appreciated! :]

I don’t know if you figure it out, but if someone is getting the same error. Is due to context variable clashing with other environment variable called context. And there’s another error in the script missing a ;.

Line 158:

unsigned int maxresults = ''' + str(options.max_results) + r''';

What I did to fix the context issue was to change the name of the variable from context to djrc_context.

And that’s it,

Derik

Next you’ll see the complete file with the changes I mentioned.

============= serach.py ==========

# Credit where credit is due. 
# This script was inspired by libBeagle https://github.com/heardrwt/RHObjectiveBeagle 
# which in turn was inspired by Saurik's 'choose' command in cycript http://www.cycript.org/
# which in turn was inspired by Apple's heap python script 'command script import lldb.macosx.heap'
# which (I think) in turn was inspired by Apple's heap_find.cpp sourcefile found here
# https://opensource.apple.com/source/lldb/lldb-179.1/examples/darwin/heap_find/heap/heap_find.cpp

# All have made great progress in their own right. 
# This tool improves upon its predecessors by adding options that lldb can use to filter queries
# For exmple, filtering all NSObject subclasses found within a given dynamic library
# i.e search NSObject -m UIKit

# MIT License
# 
# Copyright (c) 2018 Derek Selander

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import lldb
import os
import shlex
import optparse
import lldb.utils.symbolication

def __lldb_init_module(debugger, internal_dict):
    debugger.HandleCommand('command script add -f search.search search')

def search(debugger, command, result, internal_dict):
    '''
    Finds all subclasses of a class. This class must by dynamic 
    (aka inherit from a NSObject class). Currently doesn't work 
    with NSString or NSNumber (tagged pointer objects). 

    NOTE: This script will leak memory

Examples:

    # Find all UIViews and subclasses of UIViews
    find UIView

    # Find all UIStatusBar instances
    find UIStatusBar

    # Find all UIViews, ignore subclasses
    find UIView  -e

    # Find all instances of UIViews (and subclasses) where tag == 5
    find UIView -c "[obj tag] == 5"
    '''

    command_args = shlex.split(command)
    parser = generate_option_parser()
    try:
        (options, args) = parser.parse_args(command_args)
    except:
        result.SetError(parser.usage)
        return

    if not args:
        result.SetError('Usage: find NSObjectSubclass\n\nUse \'help find\' for more details')
        return

    clean_command = ('').join(args)
    

    res = lldb.SBCommandReturnObject()
    interpreter = debugger.GetCommandInterpreter()

    if options.module:
        target = debugger.GetSelectedTarget()
        module = target.FindModule(lldb.SBFileSpec(options.module))
        if not module.IsValid():
            result.SetError(
                "Unable to open module name '{}', to see list of images use 'image list -b'".format(options.module))
            return
        options.module = generate_module_search_sections_string(module, target)


    interpreter.HandleCommand('po (Class)NSClassFromString(@\"{}\")'.format(clean_command), res)
    if 'nil' in res.GetOutput():
        result.SetError('Can\'t find class named "{}". Womp womp...'.format(clean_command))
        return

    objectiveC_class = 'NSClassFromString(@"{}")'.format(clean_command)
    command_script = get_command_script(objectiveC_class, options)

    expr_options = lldb.SBExpressionOptions()
    expr_options.SetIgnoreBreakpoints(True);
    expr_options.SetFetchDynamicValue(lldb.eNoDynamicValues);
    expr_options.SetTimeoutInMicroSeconds (30*1000*1000) # 30 second timeout
    expr_options.SetTryAllThreads (True)
    expr_options.SetUnwindOnError(True)
    expr_options.SetGenerateDebugInfo(True)
    expr_options.SetLanguage (lldb.eLanguageTypeObjC_plus_plus)
    expr_options.SetCoerceResultToId(True)
    # expr_options.SetAutoApplyFixIts(True)
    frame = debugger.GetSelectedTarget().GetProcess().GetSelectedThread().GetSelectedFrame()

    if frame is None:
        result.SetError('You must have the process suspended in order to execute this command')
        return
    # debugger.HandleCommand('po ' + command_script)

    #import pdb; pdb.set_trace()
    # debugger.HandleCommand('expression -lobjc++ -g -O -- ' + command_script)
    expr_sbvalue = frame.EvaluateExpression (command_script, expr_options)
    count = expr_sbvalue.GetNumChildren() # Actually goes up to 2^32 but this is more than enough    

    if not expr_sbvalue.error.success:
        result.SetError("\n***************************************\nerror: " + str(expr_sbvalue.error) + command_script)
    else:
        if count > 1000:
            result.AppendWarning('Exceeded 1000 hits, try narrowing your search with the --condition option')
            result.AppendMessage (expr_sbvalue)
        else:
            if options.barebones:                
                for val in expr_sbvalue:
                    val_description = str(val.GetTypeName()) + ' [' + str(val.GetValue())  + ']'
                    result.AppendMessage(val_description)
            else:
                result.AppendMessage(expr_sbvalue.description)


def get_command_script(objectiveC_class, options):
    command_script = r'''//grab the zones in the current process
@import Foundation;
@import ObjectiveC;

typedef struct _DSSearchContext {
    Class query;
    CFMutableSetRef classesSet;
    CFMutableSetRef results;
} DSSearchContext;

auto task_peek = [](task_t task, vm_address_t remote_address, vm_size_t size, void **local_memory) -> kern_return_t {
    *local_memory = (void*) remote_address;
    return (kern_return_t)0;
};

vm_address_t *zones = NULL;
unsigned int count = 0;
unsigned int maxresults = ''' + str(options.max_results) + r''';
kern_return_t error = (kern_return_t)malloc_get_all_zones(0, 0, &zones, &count);

DSSearchContext *djrc_context = (DSSearchContext *)calloc(sizeof(DSSearchContext), 1);
int classCount = (int)objc_getClassList(NULL, 0);
CFMutableSetRef set = (CFMutableSetRef)CFSetCreateMutable(0, classCount, NULL);
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * classCount);
objc_getClassList(classes, classCount);


typedef struct malloc_introspection_t {
     kern_return_t (*enumerator)(task_t task, void *, unsigned type_mask, vm_address_t zone_address, memory_reader_t reader, vm_range_recorder_t recorder);
} malloc_introspection_t;

 typedef struct malloc_zone_t {
     void *reserved1[9];
     const char  *zone_name;
     void *reserved2[2];
     struct malloc_introspection_t   *introspect;
     unsigned    version;
     void *reserved3[3];
 } malloc_zone_t; 
  
for (int i = 0; i < classCount; i++) {
    Class cls = classes[i];
    CFSetAddValue(set, (__bridge const void *)(cls));
}
  
// Setup callback djrc_context
djrc_context->results = (CFMutableSetRef)CFSetCreateMutable(0, maxresults, NULL);
djrc_context->classesSet = set;
djrc_context->query =  ''' + objectiveC_class + r''';
for (unsigned i = 0; i < count; i++) {
    const malloc_zone_t *zone = (const malloc_zone_t *)zones[i];
    if (zone == NULL || zone->introspect == NULL){
        continue;
    }

    //for each zone, enumerate using our enumerator callback
    zone->introspect->enumerator(0, djrc_context, 1, zones[i], task_peek, 
    [] (task_t task, void *baton, unsigned type, vm_range_t *ranges, unsigned count) -> void {

        DSSearchContext *djrc_context =  (DSSearchContext *)baton;
        Class query = djrc_context->query;
        CFMutableSetRef classesSet = djrc_context->classesSet;
        CFMutableSetRef results = djrc_context->results;

        int maxCount = ''' + str(options.max_results) + ''';
        size_t querySize = (size_t)class_getInstanceSize(query);
      
        int (^isBlackListClass)(Class) = ^int(Class aClass) {
            NSString *className = (NSString *)NSStringFromClass(aClass);

            if ([@"_NSZombie_" isEqualToString:className]) return 1;
            if ([@"NSPlaceholderMutableString" isEqualToString:className]) return 1;
            if ([@"__ARCLite__" isEqualToString:className]) return 1;
            if ([@"__NSCFCalendar" isEqualToString:className]) return 1;
            if ([@"__NSCFTimer" isEqualToString:className]) return 1;
            if ([@"NSCFTimer" isEqualToString:className]) return 1;
            if ([@"__NSMessageBuilder" isEqualToString:className]) return 1;
            if ([@"__NSGenericDeallocHandler" isEqualToString:className]) return 1;
            if ([@"NSTaggedPointerStringCStringContainer" isEqualToString:className]) return 1;
            if ([@"NSAutoreleasePool" isEqualToString:className]) return 1;
            if ([@"NSPlaceholderNumber" isEqualToString:className]) return 1;
            if ([@"NSPlaceholderString" isEqualToString:className]) return 1;
            if ([@"NSPlaceholderValue" isEqualToString:className]) return 1;
            if ([@"Object" isEqualToString:className]) return 1;
            if ([@"NSPlaceholderNumber" isEqualToString:className]) return 1;
            if ([@"VMUArchitecture" isEqualToString:className]) return 1;
            if ([className hasPrefix:@"__NSPlaceholder"]) return 1;

            return 0;
        };
      
        for (int i = 0; i < count; i++) {
            if (CFSetGetCount(results) >= maxCount) {
                break;
            }
        
            // test 1
            if (ranges[i].size < querySize) {
              continue;
            }

        
            vm_address_t potentialObject = ranges[i].address;
         
            Class potentialClass = object_getClass((__bridge id)((void *)potentialObject));

            // test 2
            if (!(int)CFSetContainsValue(classesSet, (__bridge const void *)(potentialClass))) {
                continue;
            }
            
            // test 3
            if ((size_t)malloc_good_size((size_t)class_getInstanceSize(potentialClass)) != ranges[i].size) {
                continue;
            }
            
            // Yay, if we are here this is likely an NSObject
            if (isBlackListClass(potentialClass)) {
                continue;
            }
            
            id obj = (__bridge id)(void *)potentialObject;

            if (!(BOOL)[obj respondsToSelector:@selector(description)]) {
                continue;
            }
            '''

    if options.exact_match:
        command_script  += 'if ((int)[potentialClass isMemberOfClass:query]'
    else: 
        command_script += 'if ((int)[potentialClass isSubclassOfClass:query]'

    if options.condition:
        cmd = options.condition
        command_script += '&& (int)(' + options.condition + ')'

    command_script += r') {'

    if options.module:
        command_script += options.module

    command_script += r'''
                CFSetAddValue(results, (__bridge const void *)(obj));
            }
        }
     });
}
 
CFIndex index = (CFIndex)CFSetGetCount(djrc_context->results);
  
const void **values = (const void **)calloc(index, sizeof(id));
CFSetGetValues(djrc_context->results, values);
    
NSMutableArray *outputArray = [NSMutableArray arrayWithCapacity:index];
for (int i = 0; i < index; i++) {
    id object = (__bridge id)(values[i]);
    [outputArray addObject:object];
}
  
  
free(values);
free(set);
free(djrc_context); 
free(classes);'''

    if options.perform_action:
        command_script += r'''
[outputArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){;
    '''
        command_script += options.perform_action + ' }];\n'
     
     
    command_script += 'outputArray;'
    return command_script

def generate_module_search_sections_string(module, target):

    returnString = r'''
    uintptr_t addr = (uintptr_t)potentialClass;
    if (!('''
    section = module.FindSection("__DATA")
    for idx, subsec in enumerate(section):
        lower_bounds = subsec.GetLoadAddress(target)
        upper_bounds = lower_bounds + subsec.file_size

        if idx != 0:
            returnString += ' || '
        returnString += '({} <= addr && addr <= {})'.format(lower_bounds, upper_bounds)

    dirtysection = module.FindSection("__DATA_DIRTY")
    for subsec in dirtysection:
        lower_bounds = subsec.GetLoadAddress(target)
        upper_bounds = lower_bounds + subsec.file_size
        returnString += ' || ({} <= addr && addr <= {})'.format(lower_bounds, upper_bounds)

    returnString += ')) { continue; }\n'
    return returnString

def generate_option_parser():
    usage = "usage: %prog [options] NSObjectSubclass"
    parser = optparse.OptionParser(usage=usage, prog="search")
    parser.add_option("-e", "--exact_match",
                      action="store_true",
                      default=False,
                      dest="exact_match",
                      help="Searches for exact matches of class, i.e. no subclasses")

    parser.add_option("-c", "--condition",
                      action="store",
                      default=None,
                      dest="condition",
                      help="a conditional expression to filter hits. Objective-C input only. Use 'obj' to reference object")

    parser.add_option("-p", "--perform-action",
                      action="store",
                      default=None,
                      dest="perform_action",
                      help="Perform an action on every returned query. Objective-C input only. Use 'obj' to reference object")

    parser.add_option("-m", "--module",
                      action="store",
                      default=None,
                      dest="module",
                      help="Filters results to only be in a certain module. i.e. -m UIKit")

    parser.add_option("-b", "--barebones",
                      action="store_true",
                      default=False,
                      dest="barebones",
                      help="Only dump out the classname and pointer, no description")

    parser.add_option("-x", "--max_results",
                      action="store",
                      default=200,
                      type="int",
                      dest="max_results",
                      help="Specifies the maximum return count that the script should return")
    return parser

@rderik Thank you for sharing your solution - much appreciated! :]

Thought there was something strange without the semicolon…
Thanks buddy, Chapter 28 now works properly.