What can be gained from this article

  • Why is _objc_init executed during dyLD loading?
  • Explore some functions executed by DyLD by default during class loading that are difficult to find without symbolic breakpoints
  • _objc_init Function decomposition, mind map
  • Environ_init () Initializes the environment variable
  • How do I print environment variables on the console by setting OBJC_HELP and OBJC_PRINT_OPTIONS?
  • How to print environment variables through Terminal?
  • _read_images function decomposition, mind mapping
  • ReadClass function decomposition
  • Class load phase 1 explore flow chart
  • Debug the readClass case

To explore an overview

The introduction of _objc_init

According to the startup process of App, dyLD initializes libraries such as libsystem, libDispatch and libobJC recursively, and then calls _objc_init of LibobJC to load classes.

Set a breakpoint on the +load method

When dyLD is loading the application, the class is automatically loaded, and _objc_init cannot be found by setting the load method to break. No hint of class initialization can be found.

Set a breakpoint on the _objc_init method

Find the loading process of the class by setting a breakpoint on _objc_init

Collates the order of dyLD calls during class loading

  • Dyld ‘_DYLD_start -> dyLD load or flag
  • Dyld ‘dyLDbootstrap ::start -> DYLD bootstrap
  • Dyld ‘dyld::_main -> The main function of dyld
  • Dyld ` dyld: : initializeMainExecutable () – > the serialization of the main program
  • Dyld ` ImageLoader: : runInitializers – > image file serialization
  • Dyld ` ImageLoader: : processInitializers – > in and out of the serialization
  • Dyld ` ImageLoader: : recursiveInitialization – > recursive initialization libsystem
  • Dyld ` ImageLoader: : recursiveInitialization – > recursive initialization libdispatch
  • dyld`ImageLoaderMachO::doInitialization ->
  • dyld`ImageLoaderMachO::doModInitFunctions ->
  • Libsystem.b.dylib ‘libSystem_initializer -> libSystem serialization
  • Dylib ‘libdispatch_init -> libdispatch initialization
  • libdispatch.dylib`_os_object_init ->
  • Libobjc.a.dylib ‘_objc_init -> class load

The _OS_object_init function of libdispatch

Conclusion: Dyld will load libSysrtem and libDispatch libraries and initialize them before loading the class, which is enabled by libdisaptch. The start of the _objc_init class loading by the _OS_object_init function of libdisaptch.

_objc_init

Mind Mapping Overview:

The source code

void _objc_init(void)
{
    / / the initialized value assignment
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    // Initialization of environment variables
    environ_init(a);tls_init(a);// global static C++ function call
    static_init(a);// The runtime runtime environment is initialized with two tables registered
    runtime_init(a);// Exception capture initialization
    exception_init(a);#if __OBJC2__
    // The cache condition is initialized
    cache_t: :init(a);#endif
    // Start the callback mechanism
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
Copy the code

environ_init()

The initialization of environment variables, which can be monitored during the loading phase of the class, can be printed on the console by setting the environment variables in the project when you need them

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * environment initialization * read runtime environment variable. * Also print environment variable help if needed. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void environ_init(void) 
{
    if (issetugid()) {
        // When setuid or setgid, all environment variables are silently ignored
        // This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
        return;
    } 

    bool PrintHelp = false;
    bool PrintOptions = false;
    bool maybeMallocDebugging = false;

    // Scan environ[] directly instead of calling getenv() in bulk.
    // This optimizes the case without setting.
    for (char**p = *_NSGetEnviron(); *p ! = nil; p++) {if (0= =strncmp(*p, "Malloc".6) | |0= =strncmp(*p, "DYLD".4) | |0= =strncmp(*p, "NSZombiesEnabled".16))
        {
            maybeMallocDebugging = true;
        }

        if (0! =strncmp(*p, "OBJC_".5)) continue;
        
        if (0= =strncmp(*p, "OBJC_HELP=".10)) {
            PrintHelp = true;
            continue;
        }
        if (0= =strncmp(*p, "OBJC_PRINT_OPTIONS=".19)) {
            PrintOptions = true;
            continue;
        }
        
        if (0= =strncmp(*p, "OBJC_DEBUG_POOL_DEPTH=".22)) {
            SetPageCountWarning(*p + 22);
            continue;
        }

        const char *value = strchr(*p, '=');
        if(! *value)continue;
        value++;
        
        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            if ((size_t)(value - *p) == 1+opt->envlen  &&  
                0= =strncmp(*p, opt->env, opt->envlen))
            {
                *opt->var = (0= =strcmp(value, "YES"));
                break; }}}// Special case: enable some automatic release pool debugging
    // When some malloc debugging is enabled
    // And OBJC_DEBUG_POOL_ALLOCATION is not set to a value other than NO.
    if (maybeMallocDebugging) {
        const char *insert = getenv("DYLD_INSERT_LIBRARIES");
        const char *zombie = getenv("NSZombiesEnabled");
        const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
        if ((getenv("MallocStackLogging") | |getenv("MallocStackLoggingNoCompact")
             || (zombie && (*zombie == 'Y' || *zombie == 'y'))
             || (insert && strstr(insert, "libgmalloc"&& ()))! pooldebug ||0= =strcmp(pooldebug, "YES")))
        {
            DebugPoolAllocation = true; }}// Prints OBJC_HELP and OBJC_PRINT_OPTIONS outputs.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env); }}}Copy the code

Environment variables can be printed out on the console by setting OBJC_HELP and OBJC_PRINT_OPTIONS

Setup steps: Target -> Edit Scheme -> Run -> Arguments -> Environments Variables

Print results of environment variables:

Print environment variables through terminal

Command:

export OBJC_HELP=1
Copy the code

Terminal Print environment variable results:

static_init()

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * static_init * run the c + + static constructor. * Libc calls _objc_init() before dyld calls our static constructor,  **********************************************************************/ static void static_init() { size_t count; auto inits = getLibobjcInitializers(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { inits[i](); } auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count); for (size_t i = 0; i < count; i++) { UnsignedInitializer init(offsets[i]); init(); }}Copy the code

runtime_init

Two tables are initialized

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init(a); }Copy the code

exception_init

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * initialization exception_init libobjc exception handling system. * called by map_images(). * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
Copy the code
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * custom _objc_terminate STD: : the terminate handler. * * Uncaught exception callbacks are implemented as STD :: Terminate handlers. * 1. Check if there are active exceptions * 2. If so, check if there are Objective-C exceptions * 3. If so, the callback we registered is invoked using this object. * 4. Finally, call the previous termination handler. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // There is no exception currently
        (*old_terminate)();
    }
    else {
        // Check if it is an objC exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // If an exception occurs, call back uncaught_handler
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch(...). {// It is not an objC object.(*old_terminate)(); }}}Copy the code

_dyld_objc_notify_register

Register message events

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);
Copy the code

map_images

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
Copy the code

map_images_nolock

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // Perform the first initialization if necessary.
    // This function is called before the normal library initializers.
    // Fixme delays initialization until it finds an image using objC?
    if (firstTime) {
        preopt_init(a); }// Find all images with Objective-C metadata.
    hCount = 0;

    // Omit some logic operation......
    
    _read_images is executed when hCount has a value
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call the mirror load function after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]); }}}Copy the code

_read_images(core method)

Mind Mapping Overview:

The source code.

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * _read_images performs the initial processing of links in the header to headerList * List at the beginning. * * caller: map_images_nolock * * Locking: Map_images obtained runtimeLock * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked(a);// Condition controls the first load
    if(! doneOnce) {... }// Fix @selector confusion during precompilation
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) continue;

            bool isBundle = hi->isBundle(a); SEL *sels = _getObjc2SelectorRefs(hi, &count); UnfixedSelectors += count;for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                SEL sel = sel_registerNameNoLock(name, isBundle);
                if(sels[i] ! = sel) { sels[i] = sel; } } } } ts.log("IMAGE TIMES: fix up selector references");
    
    // Error messy class handling
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden(a);for(EACH_HEADER) {remove some code... } ts.log("IMAGE TIMES: discover classes");
    
    
    // Fix and remap classes that are not loaded by the image file
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }
    ts.log("IMAGE TIMES: remap classes");
    
    // Fix the old objc_msgSend fix calling site
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
    
     // Discover the protocol
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        NXMapTable *protocol_map = protocols(a);bool isPreoptimized = hi->hasPreoptimizedProtocols(a);bool isBundle = hi->isBundle(a);protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
    ts.log("IMAGE TIMES: discover protocols");
    
    // Fix protocols that are not loaded
    for (EACH_HEADER) {
        if (launchTime && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // Class load
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }
    ts.log("IMAGE TIMES: discover categories");
    
    // Implement non-lazy loading classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if(! cls)continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging()); }}realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // Implement classes that are not processed and optimize classes that are invaded
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");
    
    // Some code is omitted
}

Copy the code

readClass

Read the class and add it to the table

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * readClass * read by the compiler writing classes and metaclass. * Returns a new class pointer. This could be: * -cls * -nil (CLS lacks weak-linked superclasses) * - something else (the space for this class is reserved for future classes) * * Note that all the work this function does is done by * mustReadClasses(). Do not change the feature without updating it. * * Locking: By map_images or objc_readClassPair obtain runtimeLock * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName(a);// error tolerance: return nil
    if (missingWeakSuperclass(cls)) {
        // No parent class (possibly weak link).
        // Deny any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift(a); Class replacing = nil;if(mangledName ! =nullptr) {
        // Normally rW and RO are not processed here
        if (Class newCls = popFutureNamedClass(mangledName)) {
          
            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data(a);const class_ro_t *old_ro = rw->ro(a);memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls); replacing = cls; cls = newCls; }}if(headerIsPreoptimized && ! replacing) {// class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing);
        } else {
            Class meta = cls->ISA(a);const class_ro_t *metaRO = meta->bits.safe_ro(a);ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA() - >data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}
Copy the code

addNamedClass

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * addNamedClass will name = > CLS is added to the name of the metaclass mapping. * Warn against duplicate class names and keep old mappings. * lock: runtimeLock must by the caller to hold * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked(a); Class old;if ((old = getClassExceptSomeSwift(name)) && old ! = replacing) {inform_duplicate(name, old, cls);

         / / getMaybeUnrealizedNonMetaClass use the name lookup.
         // Name lookup for classes not found must be in
         // Secondary meta -> non-meta table.
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    ASSERT(! (cls->data()->flags & RO_META));
}
Copy the code

AddClassTableEntry (key function)

Add the class to the table, if addMeta is true, and add the metaclass of the current class to all tables

/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * addClassTableEntry * add a class to the table of all classes. If addMeta is true, * also automatically adds the metaclass of the class. * Lock: runtimeLock must be held by the caller. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
    runtimeLock.assertLocked(a);// This class is allowed to be known through shared caches or data segments, but is not allowed to be already in dynamic tables.
    auto &set = objc::allocatedClasses.get(a);ASSERT(set.find(cls) == set.end());

    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        // Add metaclass to all tables
        addClassTableEntry(cls->ISA(), false);
}
Copy the code

Class loading phase exploration flow chart:

Debugging readClass

Phase one: Print all

As you can see from exploring the source code, readClass is a function that reads the class’s methods, so add print events to this function

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName(a);printf("%s - BBlv - %s\n",__func__, mangledName); .Copy the code

The console outputs partial results

readClass - BBlv - __NSStackBlock__ readClass - BBlv - __NSMallocBlock__ readClass - BBlv - __NSAutoBlock__ readClass - BBlv - __NSFinalizingBlock__ readClass - BBlv - __NSGlobalBlock__ readClass - BBlv - __NSBlockVariable__ readClass - BBlv - OS_object readClass - BBlv - OS_dispatch_object readClass - BBlv - OS_dispatch_queue readClass - BBlv - OS_dispatch_channel readClass - BBlv - OS_dispatch_source readClass - BBlv - OS_dispatch_mach readClass - BBlv - OS_dispatch_queue_runloop readClass - BBlv - OS_dispatch_semaphore readClass - BBlv - OS_dispatch_group readClass - BBlv  - OS_dispatch_workloop readClass - BBlv - OS_dispatch_queue_serial readClass - BBlv - OS_dispatch_queue_concurrent readClass - BBlv - OS_dispatch_queue_main readClass - BBlv - OS_dispatch_queue_global readClass - BBlv - OS_dispatch_queue_pthread_root readClass - BBlv - OS_dispatch_queue_mgr readClass - BBlv - OS_dispatch_queue_attr readClass - BBlv - OS_dispatch_mach_msg readClass - BBlv - OS_dispatch_io readClass - BBlv - OS_dispatch_operation Readclass-bblv-os_dispatch_disk omits a large amount of output information...... readClass - BBlv - LGTeacher readClass - BBlv - LGPerson readClass - BBlv - FFPersonCopy the code

Specific operation diagram:

Stage two: Accurate printing

To add conditions to print, I only care about the class I created, not the system class, the code modified as follows:

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->nonlazyMangledName(a);const char *ffPersonName = "FFPerson";
    
    if (strcmp(mangledName, ffPersonName) == 0) {
        printf("%s - BBlv - %s\n",__func__, mangledName);        
    }
Copy the code

Print result:

readClass - BBlv - FFPerson
Copy the code

Detailed operation diagram