首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么不同项目的构建在使用iOS在设备上的编译器/工具链“不允许操作”时会失败?

为什么不同项目的构建在使用iOS在设备上的编译器/工具链“不允许操作”时会失败?
EN

Stack Overflow用户
提问于 2020-06-13 11:25:42
回答 1查看 578关注 0票数 1

我是一个中等技能的Linux/Unix用户,试图在(越狱的) iPad上为iPad编译软件。

许多构建(例如,make和tex-live)由于一些Operation not permitted错误而失败。这看起来像Can't exec "blah": Operation not permittedexecvp: blah: Operation not permitted,其中blahaclocalconfigure脚本、libtool,或者几乎任何东西。奇怪的是,在Makefileconfigure脚本中查找违规行并以sudo -u mobile -E作为前缀将解决该行的错误,只会在以后的行或其他文件中重新出现。由于我正在以mobile的形式运行构建脚本,我不明白这如何解决这个问题,但它确实解决了这个问题。我已经确认,进行这些更改实际上允许脚本在此之前成功地工作。使用sudosudo -u mobile -E运行构建脚本和/或以root的形式运行整个构建并不能解决问题;无论是哪一种,我仍然必须编辑构建脚本以添加sudo

我想知道为什么会发生这种情况,如果可能的话,我可以在不编辑构建脚本的情况下解决这个问题。关于这些类型的错误的任何信息对我来说都会很有趣,即使它们不能解决我的问题。我知道权限/安全/权限系统在iOS上是不寻常的,我想了解更多关于它是如何工作的。

我正在使用iPad Pro 4在越狱的iOS 13.5上使用sbingner和MCApollo的repos的构建工具(repo.bingner.com和mcapollo.github.io/Public)。特别是,我使用的是LLVM 5构建(手动安装自sbingner的旧的debs)、Clang 10、达尔文CC工具927和GNU Make 4.2.1。我已经将CCCXXCFLAGS等设置为指向clang-10和带有-isysroot的iOS 13.5SDK,并确认这些设置是有效的。我想用更新的版本来代替这些工具,但由于这个问题和其他几个问题,我还不能为自己构建这些工具。如果有必要的话,我可以访问Mac进行交叉编译,但我宁愿只使用我的iPad,因为我喜欢挑战。

我可以附上任何必要的日志或提供更多的信息,如果这将是有用的;我不知道这个问题足够知道什么信息是有用的。提前谢谢你帮我!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-01-07 22:27:45

对于那些最终需要在没有修复此问题的越狱中解决这个问题的人,我已经编写了(粘贴在下面)一个基于来自苹果xnu内核源代码的posix_spawn实现的userland钩子。

使用Theos编译它,并通过将环境变量DYLD_INSERT_LIBRARIES设置为生成的dylib的路径将其注入到shell生成的所有进程中。注意:一些经过调整的注入器(即libhooker,请参见这里)重置DYLD_INSERT_LIBRARIES,因此如果您注意到此行为,请确保只注入库。

因为iOS中的exec系统的实现调用了posix_spawn,所以这个钩子修复了到目前为止遇到的所有与exec相关的问题。

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <spawn.h>

// Copied from bsd/kern/kern_exec.c
#define IS_WHITESPACE(ch) ((ch == ' ') || (ch == '\t'))
#define IS_EOL(ch) ((ch == '#') || (ch == '\n'))
// Copied from bsd/sys/imgact.h
#define IMG_SHSIZE 512

// Here, we provide an alternate implementation of posix_spawn which correctly handles #!.
// This is based on the implementation of posix_spawn in bsd/kern/kern_exec.c from Apple's xnu source.
// Thus, I am fairly confident that this posix_spawn has correct behavior relative to macOS.
%hookf(int, posix_spawn, pid_t *pid, const char *orig_path, const posix_spawn_file_actions_t *file_actions, const posix_spawnattr_t *attrp, char *const orig_argv[], char *const envp[]) {
    
    // Call orig before checking for anything.
    // This mirrors the standard implementation of posix_spawn because it first checks if we are spawning a binary.
    int err = %orig;
    
    // %orig returns EPERM when spawning a script.
    // Thus, if err is anything other than EPERM, we can just return like normal.
    if (err != EPERM) 
        return err;
    
    // At this point, we do not need to check for exec permissions or anything like that.
    //  because posix_spawn would have returned that error instead of EPERM.

    // Now we open the file for reading so that we can check if it's a script.
    // If it turns out not to be a script, the EPERM must be from something else
    //  so we just return err.

    FILE *file = fopen(orig_path, "r");
    if (file == NULL) {
        return err;
    }
    if (fseek(file, 0, SEEK_SET)) {
        return err;
    }   

    // In exec_activate_image, the data buffer is filled with the first PAGE_SIZE bytes of the file.
    // However, in exec_shell_imgact, only the first IMG_SHSIZE bytes are used.
    // Thus, we read IMG_SHSIZE bytes out of our file.
    // The buffer is filled with newlines so that if the file is not IMG_SHSIZE bytes,
    //  the logic reads an IS_EOL.
    char vdata[IMG_SHSIZE] = {'\n'};
    if (fread(vdata, 1, IMG_SHSIZE, file) < 2) { // If we couldn't read at least two bytes, it's not a script.
        fclose(file);
        return err;
    }

    // Now that we've filled the buffer, we don't need the file anymore.
    fclose(file);
    
    // Now we follow exec_shell_imgact.
    // The point of this is to confirm we have a script 
    //  and extract the usable part of the interpreter+arg string.
    // Where they return -1, we don't have a shell script, so we return err.
    // Where they return an error, we return that same error.
    // We don't bother doing any SUID stuff because SUID scripts should be disabled anyway.
        char *ihp;
        char *line_startp, *line_endp;
        
    // Make sure we have a shell script.
        if (vdata[0] != '#' || vdata[1] != '!') {
        return err;
    }
        
        // Try to find the first non-whitespace character
        for (ihp = &vdata[2]; ihp < &vdata[IMG_SHSIZE]; ihp++) {
                if (IS_EOL(*ihp)) {
                        // Did not find interpreter, "#!\n"
                        return ENOEXEC;
                } else if (IS_WHITESPACE(*ihp)) {
                        // Whitespace, like "#!    /bin/sh\n", keep going.
                } else {
                        // Found start of interpreter
                        break;
                }
        }
        
        if (ihp == &vdata[IMG_SHSIZE]) {
                // All whitespace, like "#!           "
                return ENOEXEC;
        }

        line_startp = ihp;
        
        // Try to find the end of the interpreter+args string
        for (; ihp < &vdata[IMG_SHSIZE]; ihp++) {
                if (IS_EOL(*ihp)) {
                        // Got it
                        break;
                } else {
                        // Still part of interpreter or args
                }
        }
        
        if (ihp == &vdata[IMG_SHSIZE]) {
                // A long line, like "#! blah blah blah" without end
                return ENOEXEC;
        }
        
        // Backtrack until we find the last non-whitespace
        while (IS_EOL(*ihp) || IS_WHITESPACE(*ihp)) {
                ihp--;
        }
        
        // The character after the last non-whitespace is our logical end of line
        line_endp = ihp + 1;
        
        /*
         * Now we have pointers to the usable part of:
         *
         * "#!  /usr/bin/int first    second   third    \n"
         *      ^ line_startp                       ^ line_endp
         */

    // Now, exec_shell_imgact copies the interpreter into another buffer and then null-terminates it.
    // Then, it copies the entire interpreter+args into another buffer and null-terminates it for later processing into argv.
    // This processing is done in exec_extract_strings, which goes through and null-terminates each argument.
    // We will just do this all at once since that's much easier.
    
    // Keep track of how many arguments we have.
    int i_argc = 0;

    ihp = line_startp;
    while (true) {
        // ihp is on the start of an argument.
        i_argc++;
        // Scan to the end of the argument.
        for (; ihp < line_endp; ihp++) {
            if (IS_WHITESPACE(*ihp)) {
                // Found the end of the argument
                break;
            } else {
                // Keep going
            } 
        }
        // Null terminate the argument
        *ihp = '\0';
        // Scan to the beginning of the next argument.
        for (; ihp < line_endp; ihp++) {
            if (!IS_WHITESPACE(*ihp)) {
                // Found the next argument
                break;
            } else {
                // Keep going
            }
        }
        if (ihp == line_endp) {
            // We've reached the end of the arg string
            break;
        }
        // If we are here, ihp is the start of an argument.
    }
    // Now line_startp is a bunch of null-terminated arguments possibly padded by whitespace.
    // i_argc is now the count of the interpreter arguments.
       
    // Our new argv should look like i_argv[0], i_argv[1], i_argv[2], ..., orig_path, orig_argv[1], orig_argv[2], ..., NULL
    //  where i_argv is the arguments to be extracted from line_startp;
    // To allocate our new argv, we need to know orig_argc.
    int orig_argc = 0;
    while (orig_argv[orig_argc] != NULL) {
        orig_argc++;
    }
    
    // We need space for i_argc + 1 + (orig_argc - 1) + 1 char*'s
    char *argv[i_argc + orig_argc + 1];
    
    // Copy i_argv into argv
    int i = 0;
    ihp = line_startp;
    for (; i < i_argc; i++) {
        // ihp is on the start of an argument
        argv[i] = ihp;
        // Scan to the next null-terminator
        for (; ihp < line_endp; ihp++) {
            if (*ihp == '\0') {
                // Found it
                break;
            } else {
                // Keep going
            }
        }
        // Go to the next character
        ihp++;
        // Then scan to the next argument. 
        // There must be another argument because we already counted i_argc.
        for (; ihp < line_endp; ihp++) {
            if (!IS_WHITESPACE(*ihp)) {
                // Found it
                break;
            } else {
                // Keep going
            }
        }
        // ihp is on the start of an argument.
    }

    // Then, copy orig_path into into argv.
    // We need to make a copy of orig_path to avoid issues with const.
    char orig_path_copy[strlen(orig_path)+1];
    strcpy(orig_path_copy, orig_path);  
    argv[i] = orig_path_copy;
    i++;

    // Now, copy orig_argv[1...] into argv.
    for (int j = 1; j < orig_argc; i++, j++) {
        argv[i] = orig_argv[j];
    }
    // Finally, add the null.
    argv[i] = NULL;
    // Now, our argv is setup correctly. 

    // Now, we can call out to posix_spawn again.
    // The interpeter is in argv[0], so we use that for the path.
    return %orig(pid, argv[0], file_actions, attrp, argv, envp);
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/62359060

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档