精华内容
下载资源
问答
  • 文章目录《编译原理》课程设计实验报告书C-语言的语法描述系统设计系统亮点符号表的实现中间代码生成系统的总体结构主要功能模块的设计系统运行流程系统实现系统主要函数说明(主要功能、输入\输出、实现思想)...

    作者:shmily

    C-语言的语法图描述

    在这里插入图片描述

    系统设计

    系统亮点

    符号表的实现

    符号表采用了哈希表的形式,可以方便地查找、插入和删除,但是问题也随之而来,就是符号的作用于较难跟踪。很有可能同一名称的变量在不同作用于出现,如果孤立地对待每一个变量便会出现变量冲突。因此我们的符号表将作用域做了栈式处理,例如下图:

    在这里插入图片描述

    再将栈中每一个元素,即每一个作用域做延伸,对每一个作用域建立一个哈希表,将范围缩小。这样一来可以对每一个变量框在一个作用域中进行跟踪。总体数据结构如下:

    在这里插入图片描述

    中间代码生成

    我们的中间代码生成运用一遍扫描的方法自下而上归约,但是和课本的方法有出入,进行了一些改变。首先,建立一个链表存储所有的生成的四元式,每个四元式的本质是一个结构体。其中,在对if-else或是while这种需要回填的部分处理时,先对判断条件进行判断,结果存在一个临时变量t之中,这个变量的值非0即1.

    当面临if语句规约时,判断的是t的值,真出口就是继续执行的下一条语句,执行完语句之后,我们生成了又一个临时变量L,并且新增一个操作叫做label,它用来指示整个if-else语句的结尾,这个label四元式是在控制语句的结尾,当生成(label,L2,-,-)的同时,顺序查找四元式链表中j_if_false后紧跟的(j,-,-,-),并将其填为(j,L2,-,-),代表无条件跳转到L2。

    对于假出口的处理同理,只不过是在else语句开始之前就生成一个(label,L1,-,-),记下标号,并且在生成之后顺序查找四元式链表,找到j_if_false开头的代码进行回填。这样,真假出口的回填就完成了。

    其他的四元式根据不同节点的类型直接生成不同的四元式,最后将四元式链表输出到文件当中即可。
    在这里插入图片描述

    系统的总体结构

    结构框图如图所示,共分为五大部分以及两个辅佐部分,五大部分为词法分析、语法分析、建立符号表、类型检查和中间代码,辅佐部分为符号表和语法树。

    在这里插入图片描述

    主要功能模块的设计

    **词法分析和语法分析:**利用flex & bison工具实现,在cminus.l文件中写好词法规则和getToken函数,在cminus.y文件中写好BNF语法和其他相关函数即可,flex和bison可以帮助我们生成lex.yy.c文件和.tab.文件。

    **语义分析:**生成语法分析树和符号表,借助这两个数据结构分析语义。

    **中间代码生成:**通过一遍扫描和回填技术实现中间代码生成。

    系统运行流程

    C-minus编译器运行于Linux系统,在/compiler文件下执行sh脚本即可编译运行整个项目,例如测试gcd.c文件:

    ./compiler.sh ../testcase/gcd.c
    

    之后在/testcase文件中可以查看该测试样例对应生成的中间代码,记录在一个.txt文件中。

    在这里插入图片描述

    系统共分为三个部分:词法分析器、语法分析器、语义分析与中间代码产生器。

    首先,编译器会对输入的文件进行词法分析,分割成一个个的单词符号,其次,进行语法分析,识别出各类语法单位,判断输入串是否构成语法上正确的“程序”,最后按照语义规则对语法分析器归约出的语法单位进行语义分析并把他们翻译成一定形式的中间代码。

    系统实现

    系统主要函数说明(主要功能、输入\输出、实现思想)

    cminus.l

    这一文件的主要功能为flex分析器的文件。第一部分为定义,flex工具在处理这一部分的时候,会将第一部分的所有代码原封不动的插入到 lex.yy.c(词法分析代码)中,我们在这一部分中插入了程序需要用到的头文件等内容。第二部分是词法规则部分:正则表达式+代码片段。当匹配相对应的正则表达式时,这些代码片段就会被执行。我们的代码片段为一个单词符号的编码。最后一个部分包括辅助函数,它们用于在第2个部分被调用且不在任何地方被定义的辅助程序。

    cminus.y

    这一文件的主要功能是产生语法分析代码。首先是定义部分:定义部分包括bison需要用来建立分析程序的有关记号、数据类型以及文法规则的信息,还包括了一些头文件。规则部分:包括修改的BNF格式中的文法规则以及将在识别出相关的文法规则时被执行的C代码中的动作。我们根据每一条产生式执行不同的语义动作,例如生成节点、赋上相应属性等等。最后是用户自定义函数部分,我们定义了几个插入节点的函数,可以和语义动作对应起来。最后这个文件会被建立成两个.tab.程序,实现语法分析和语法树的建立。

    具有一定的检错能力:例如对这样一行代码

    rem = x-(div*10;
    

    缺少匹配的右括号,在编译器中便停在了这一行的;处,并报语法错误。

    在这里插入图片描述

    buildSymtab

    Cminus的语义分析器在analyze.csymtab.c中,其对应的主要功能,analyse.c是用于语义分析本身,而symtab.c则是用于生成其对应的符号表。

    进入语义分析部分的代码在main.c中:

    #if !NO_ANALYZE
        if (! Error) {
            if (TraceAnalyze) fprintf(listing, "\nBuilding the symbol table...\n");
            buildSymtab(syntaxTree);
            if (TraceAnalyze) fprintf(listing, "\nChecking type...\n");
            typeCheck(syntaxTree);
            if (TraceAnalyze) fprintf(listing, "\nType check completed!\n");
        }
    

    在语义分析之前,编译器最开始的输入是一段代码,经过词法分析,输出的是词法单元,从而被语法分析单元所识别;语法分析的输入是一个个词法单元,通过分析这些词法单元之间的逻辑,利用递归下降等方法,形成一棵语法树,并将语法树的根结点存储在一个TreeNode类中,从而通过根结点就可以实现对于整个语法树的遍历(一般是前序遍历);之后,来到了语义分析部分,语义分析的输入是一个语法树,这里我们的输入是根结点;语义分析的输出,则是符号表和语法报错信息。

    符号表的意义在于,分析代码中所有的声明,比如变量函数等内容;而语法报错信息,则会通过语法树结点关系,检测相邻词法单元是否符合文法规则:比如,int 1和int a两种输入,在语法分析阶段均可通过,但是在语义分析阶段,int 1会被识别为一个错误,因为根据语法规则,int是一个声明,声明后面只能跟着一个变量名ID,而词法单元1的属性是NUM,int后面是不允许接着一个NUM的。

    函数buildSymtab构造符号,通过语法树的遍历构建。

    /* Function buildSymtab constructs the symbol
     * table by preorder traversal of the syntax tree
     */
    void buildSymtab(TreeNode * syntaxTree) {
    	globalScope = sc_create((char *) "ESCOPO_GLOBAL");
    	sc_push(globalScope);
    	traverse(syntaxTree, insertNode, afterInsertNode);
    	sc_pop();
        sys_free();
    	if(mainDeclaration == FALSE) {
    		fprintf(listing, "Declaration Error: Undeclared main function\n");
        	return;
    	}
     	if (TraceAnalyze) {
    		fprintf(listing,"\nSymbol Table:\n");
        	printSymTab(listing);
      	}
    }
    

    该函数共分为四个步骤,首先调用sc_create()函数创建一个作用域栈,作用域栈用于储存一个符号的作用域,如果给出作用域名称作为参数,它将分配内存并填写每个属性。在初始化的时候,如果是全局变量,则它就是最大的全局作用域,如果不是,则把指针设置为该作用域所属的较大作用域的父作用域。
    在这里插入图片描述

    例如,名为main的作用域将global作为其父作用域。 在main中创建另一个作用域时,它以相同的方式堆叠在栈上。 当main结束时(遇到复合语句时),main作用域弹出并查看全局作用域中的其余TreeNodes。

    Scope sc_create(char * funcName) {
        Scope newScope = (Scope) malloc(sizeof(struct ScopeRec));
        newScope->funcName = funcName;
        newScope->tamanhoBlocoMemoria = 0;
        if(!strcmp(funcName, "ESCOPO_GLOBAL")) {
            newScope->parent = NULL;
        } else {
            newScope->parent = globalScope;
        }
        scopes[nScope++] = newScope;
        return newScope;
    }
    

    之后,调用sc_push()函数,将全局作用域先推入栈中:

    void sc_push(Scope scope) {
        scopeStack[nScopeStack++] = scope;
    }
    

    接着,调用traverse()函数,它采用深度遍历,即先遍历preProc(t)以及它的子节点,再遍历postProc(t)。

    /* Procedure traverse is a generic recursive
     * syntax tree traversal routine:
     * it applies preProc in preorder and postProc
     * in postorder to tree pointed to by t
     */
    static void traverse( TreeNode * t,
                   void (* preProc) (TreeNode *),
                   void (* postProc) (TreeNode *) )
    { if (t != NULL)
      { preProc(t);
        { int i;
          for (i=0; i < MAXCHILDREN; i++)
            traverse(t->child[i],preProc,postProc);
        }
        postProc(t);
        traverse(t->sibling,preProc,postProc);
      }
    }
    

    最后,调用printSymTab()打印函数,间接调用printSymTabRows():打印符号表的一行和insertNode():把一个节点插入到符号表中,最终将打印出所有的函数名和每个函数里所有的变量名,打印项目有名字、id类型、数据类型、作用域、大小(以字为单位)、相对位置和在源程序中出现的行数。

    在这里插入图片描述

    st_insert()、st_create()

    将bucketList添加到作用域栈中, 使用hash函数获取哈希值。 从scope_top搜索指定的作用域名称,直到找到正确的作用域,并将bucketList放在指定作用域的bucket数组中的哈希值处。 此时,如果没有相同的bucketList,则添加一个新的bucketList。至此,符号表的结构可以表示为下图:
    在这里插入图片描述

    void st_insert(char * name, int lineno, int loc, TreeNode * treeNode, int tamanho) {
    	int h = hash(name);
    	Scope top = sc_top();
    
        if(top->hashTable[h] == NULL) {
    	// Add scope to syntax tree node
            treeNode->kind.var.scope = top;
            top->hashTable[h] = st_create(name, lineno, loc, treeNode, tamanho);
            treeNode->kind.var.scope->tamanhoBlocoMemoria += tamanho;
        } else {
            BucketList l = top->hashTable[h];
            while ((l->next != NULL) && (strcmp(name, l->name))) {
                l = l->next;
            }
            if (l->next == NULL && (strcmp(name, l->name))) { /* Variable not yet in table */
                //Add scope to syntax tree node
                treeNode->kind.var.scope = top;
                //Add a new item to the symbol table
                l->next = st_create(name, lineno, loc, treeNode, tamanho);
                treeNode->kind.var.scope->tamanhoBlocoMemoria += tamanho;
            }
        }
    } /* st_insert */
    
    BucketList st_create(char * name, int lineno, int loc, TreeNode * treeNode, int tamanho) {
        BucketList l = (BucketList) malloc(sizeof(struct BucketListRec));
        l->name = name;
        l->lines = (LineList) malloc(sizeof(struct LineListRec));
        l->lines->lineno = lineno;
        l->lines->next = NULL;
        l->memloc = loc;
        l->tamanho = tamanho;
        l->treeNode = treeNode;
        l->next = NULL;
        return l;
    } /* st_create */
    

    st_lookup()、st_lookup_top()

    st_lookup()从当前堆栈顶部的范围中查找父范围。st_lookup_top仅在当前作用域内查找,它以范围名称作为参数,并仅检查该范围的BucketList。

    typeCheck()

    在建立完符号表之后,语义分析的下一个任务是对于语法错误的检查,该部分的实现原理是,通过检查相邻语法树结点的词法属性,确保是符合常规的。具体的实现函数在analyse.c中的type check函数:

    /* Procedure typeCheck performs type checking
     * by a postorder syntax tree traversal
     */
    void typeCheck(TreeNode * syntaxTree) {
    	traverse(syntaxTree, beforeCheckNode, checkNode);
    }
    

    typeCheck直接调用了一个遍历函数traverse,传入的三个参数,第一个是syntaxTree,代表语法树,第二个beforeCheckNode,它是一个函数,代表遍历的终止条件,即遍历到叶子结点时,语法树遍历停止,第三个函数则是需要重点分析的checkNode,它制定了语法规则分析的过程。

    static void beforeCheckNode(TreeNode * t) {
    	if (t->node == EXPK) {
    		if (t->kind.var.varKind == FUNCTIONK) {
    			funcName = t->kind.var.attr.name;
    		}
      	}
    }
    

    check node函数的工作原理是,一边遍历语法树,遍历到当前的一个结点之后,检查该结点的相邻结点是否符合语法规则,如果不符合就报错,如果符合就可以继续。

    例如:如果该节点是t->node == EXPK,那么它有三种情况,第一种是赋值语句,那么它的孩子节点的类型必须相同;如果是判断表达式,那么结点类型必须是void;如果是算术表达式,它的类型必须是int,这样对应起来。

    /* Procedure checkNode performs
     * type checking at a single tree node
     */
    static void checkNode(TreeNode * t) {
        if (t->node == STMTK) {
            switch (t->kind.stmt) {
                case INTEGERK: t->child[0]->type = INTEGER_TYPE; break;
                case VOIDK:
                    if (t->child[0]->kind.var.varKind == IDK) {
                        typeError(t, "Variable cannot be void!");
                    } else {
                        t->child[0]->type = VOID_TYPE;
                    }
                    break;
                case IFK: break;
                case WHILEK: break;
                case RETURNK: break;
                case COMPK: break;
            }
        } else if (t->node == EXPK) {
            switch (t->kind.exp) {
                case ATRIBK:
                    if ((t->child[0]->type != INTEGER_TYPE) || (t->child[1]->type != INTEGER_TYPE)) {
                        typeError(t, "Invalid assignment, int value expected and void received");
                    } else  {
                        t->type = INTEGER_TYPE;
                    }
                    break;
                case RELK: t->type = VOID_TYPE; break;
                case ARITHK: t->type = INTEGER_TYPE; break;
            }
        } else if (t->node == VARK) {
            switch (t->kind.var.varKind) {
                case IDK: t->type = INTEGER_TYPE; break;
                case VECTORK: t->type = INTEGER_TYPE; break;
                case CONSTK: t->type = INTEGER_TYPE; break;
                case FUNCTIONK: break;
                case CALLK: break;
            }
        }
    }
    

    至此,语义分析结束了。

    codeGen()

    接下来的中间代码生成则也是对于语法树进行操作,传入一棵语法树,从根结点,根据该节点的词法属性,分析词法结点之间的逻辑,翻译成四元式。

    进入中间代码生成部分的代码在main.c中,将中间代码写入同名的txt文件中,strcspn函数的作用是,在pgm字符串中查找到第一个“.”,并返回它之前的所有字符作为子串,这样做的目的在于,我们传入给编译器的文件是一个文件,我们中间代码的输出结果也需要保存在一个文件中,输出文件在这里这样做,是为了保持和输入文件同名。strncpy函数的作用是拷贝字符串,这里用于将strcspn提取的文件名存入字符串供输出文件使用。

    #if !NO_CODE
        if (! Error) {
            char * codefile;
            int fnlen = strcspn(codeInfo.pgm, ".");
            codefile = (char *) calloc(fnlen + 4, sizeof(char));
            strncpy(codefile, codeInfo.pgm, fnlen);
            strcat(codefile, ".txt");
            code = fopen(codefile, "w");
            if (code == NULL) {
                printf("Cannot open the file %s\n", codefile);
                exit(1);
            }
            if (TraceCode) fprintf(listing, "\nGenerate intermediate code...\n");
            codeGen(syntaxTree, codefile, codeInfo);
            free(syntaxTree);
            fclose(code);
            if (TraceCode) fprintf(listing, "\nIntermediate code generation is complete!\n");
        }
    

    codeGen函数传入了语法树根结点以及需要写入的文件,进行后续操作

    /* Procedure codeGen generates code to a code
     * file by traversal of the syntax tree. The
     * second parameter (codefile) is the file name
     * of the code file, and is used to print the
     * file name as a comment in the code file
     */
    void codeGen(TreeNode * syntaxTree, char * codefile, CodeInfo codeInfo) {
        cGen(syntaxTree);
            // If it is a Common Program code, add SYSCALL to the end of the code.
        insertQuad(createQuad(SYSCALL, NULL, NULL, NULL));
        emitCode("********** Intermediate Code **********\n");
        printIntermediateCode();
    }
    

    这和函数主要调用了 cGen()、insertQuad()和printIntermediateCode()

    insertQuad()

    生成四元式函数。四元式由一个结构体构成。

    Quadruple * insertQuad(Quadruple q) {
        Quadruple * ptr = (Quadruple *) malloc(sizeof(struct Quad));
        if(head == NULL) {
            head = q;
            head->next = NULL;
            ptr = &head;
        } else {
            Quadruple temp = head;
            while(temp->next != NULL) {
                temp = temp->next;
            }
            temp->next = q;
            temp->next->next = NULL;
            ptr = &temp->next;
        }
        return ptr;
    }
    

    cGen()

    static void cGen(TreeNode * tree) {
        if (tree != NULL) {
            switch (tree->node) {
                case STMTK:
                    genStmt(tree);
                    break;
                case EXPK:
                    genExp(tree);
                    break;
                case VARK:
                    genVar(tree);
                    break;
                default:
                    break;
            }
            /*If the number of parameters is greater than 0, cGen () will be called automatically*/
            if(paramHead == NULL) {
                cGen(tree->sibling);
            } else {
                if(paramHead->count == 0) {
                    cGen(tree->sibling);
                }
            }
        }
    }
    

    可以看到,传入的语法树在一边遍历的同时,检查结点的类型,分为三种语句结点,一种是STMTK,主要分析保留字和复合语句;一种是EXPK,主要分析表达式,一种是VARK,主要分析不同类型的变量。

    **genStmt():**该函数的作用主要是处理Cminus中所包含的关键字——int,void,if,while,return和compound。以if作为一个例子来分析说明这个函数需要做的工作:if结点包括三个子结点:if本身的判断表达式、复合语句、以及else。我们对于每个if的子结点递归分析,因为if的子结点可能也会是一个表达式,比如if的条件判断,这样的话就需要对它进行递归分析。

     case IFK:
                p1 = tree->child[0];
                p2 = tree->child[1];
                p3 = tree->child[2];
                /* Generate code for test expression */
                cGen(p1);
                /* Assigns as the first operand*/
                op1 = operandoAtual;
                /* Assigns instruction type */
                instrucaoAtual = JP;
                /* Create and insert a new intermediate code representation*/
                q = insertQuad(createQuad(instrucaoAtual, op1, NULL, NULL));
                /* Save if's IR to update with label representing end of block */
                pushLocation(q);
                /* Generate code for block */
                cGen(p2);
                /* set second operand */
                op2 = createOperand();
                op2->kind = String;
                op2->contents.variable.name = createLabelName();
                op2->contents.variable.scope = tree->kind.var.scope;
                /* update if intermediate code instruction */
                updateLocation(op2);
                popLocation();
                if(p3 != NULL) {
                    q = insertQuad(createQuad(GOTO, NULL, NULL, NULL));
                    pushLocation(q);
                }
                /* Label used to mark end of block */
                insertQuad(createQuad(LBL, op2, NULL, NULL));
                cGen(p3);
    
                if(p3 != NULL) {
                    op1 = createOperand();
                    op1->kind = String;
                    op1->contents.variable.name = createLabelName();
                    op1->contents.variable.scope = tree->kind.var.scope;
                    /* update if intermediate code instruction */
                    updateLocation(op1);
                    popLocation();
    
                    /* Label used to mark end of block else*/
                    insertQuad(createQuad(LBL, op1, NULL, NULL));
                }
                break; /* IFK */
    

    instrucaoAtual用来标记四元式的第一区间,即操作类型,它是一个枚举类型,记录在cgen.h中:

    typedef enum instrucao {ADD, SUB, MULT, _DIV, MOD,
        VEC, VEC_ADDR,
        EQ, NE, _LT, LET,_GT, GET, ASN,
        FUNC, RTN, GET_PARAM, SET_PARAM, CALL, PARAM_LIST,
        JP, GOTO, LBL, SYSCALL, HALT} InstructionKind;
    

    pushLocation()用于保存if的地址,记录递归的返回位置,待分析完这一条路径之后,就可以找到函数在哪里被调用了,在调用的过程中,由于各个节点都需要递归地访问,实现回填。

    其他的处理也是类似的,比如在while语句里面,包含的是两个结点,一个是循环它本身,第二个是与之对应的条件,同if一样,分为两块进行分别的一个递归处理。

    **genExp():**处理表达式结点的逻辑比较简单,如果表达式的结点类型是ID或者数字,就直接记录它,但有一个需要特殊处理的地方就是数组,在赋值中,如果左操作数是数组,则存储此变量的内存位置。如果结点是一个运算符,那么据我们所知,运算符是由子结点构成的,它的子结点就是运算的两个数字,或者是ID字符。我们需要使用LD命令将子结点里面的具体数字或字符读取出来,再根据运算符的类型构造相应的四元式。

    **genVar():**对于CONSTK、IDK、VECTORK,FUNCTIONK,在四元式中记录他们的类型和名字、CALLK节点记录跳转的函数,参数个数和参数列表的地址。

    最终生成的四元式结果如下:
    在这里插入图片描述

    课程设计心得

    ​ 这次实验最大的体会就是,阅读代码的能力也很重要!!我们组最一开始完全没有任何头绪,于是上github搜集了很多资料,也看过老师给的资料。但是发现这些代码和我们的题目都不符合,于是听取了一位大神的建议,搬出了老大哥——gcc编译器的源码,果然!不愧是自称第一编译器,代码和注释都写的非常流畅,虽然要比我们做的实验复杂的多,但是尤其是中间代码的处理部分非常值得借鉴。与此同时我也发现了,书上学的和实际用的并没有什么很大的关联,有的只是思路,等到真正实践的时候还是要自己对理论进行适当的修改。

    临近期末考试增多,还是有很多粗糙的地方,有一些代码只能说是“复现”出来的,但是也让我们重新认识了我们天天打交道的编译器到底是怎么回事,甚至萌生了寒假不借助flex & bison写一个编译器的想法。

    总之,十分感谢这次实验,不过希望老师下次能够早些放出实验题目,让我们有更充分的时间去钻研。

    源码

    需要可留言

    展开全文
  • 硬件设计流程硬件项目总体流程图原理图的设计流程PCB的制作流程注意事项原理图PCB 硬件项目总体流程图 原理图的设计流程 采用自上而下(或者自下而上)的设计方案,先在顶层图纸Hardware_Architecture中画出...

    硬件项目总体流程图

    visio制作流程图



    原理图的设计流程

    1. 采用自上而下(或者自下而上)的设计方案,先在顶层图纸Hardware_Architecture中画出各个模块,在放置sheet entry将部分模块进行连接:
      通过 Design >> Creat Sheet From Sheet Symbol 至相应的模块中绘制
    2. 原理图进行编译检查,排查可能存在的错误(部分警告可忽略),如元器件未连接,元件名重复,如:
      Floating Power Object GND =>> 肉眼很难分辨悬空,放大结点,重新连接
      Duplicate Component Designators C19 at 668,972 and 795,650 =>> 元器件标号重复
      Net AA10 has no driving source (Pin U11-A20,Pin U14-26) =>> 输入型引脚未连接或没有信号出入
      Off sheet Pin -3 at 1594,608 =>> 原理图图纸小
      Extra Pin U31-1 in Normal of part U31A =>> 封装不可用,重新加载一下PCB封装
    3. 将画好的原理图的元器件的封装逐个检查,保证元器件的封装无误


    PCB的制作流程

    1. 绘制PCB的大小,如果是产品的改进,最好兼容以前的外部封装。(在mechanical 或 keep out 层画板框)
    2. 确定PCB的层数,如在设置四层板时,内电层有正片和负片两种选择方式,像上、下两层的signal为正片,即在铺铜时,显示连接区域的铜片,负片正好相反。负片/正片电源分隔时,隔离距离应大于1mm,即线宽需大于1mm。
    3. PCB规则的设置,设置好一个PCB规则,如线宽,间距,过孔大小,元器件的间距,高度等因素,当触犯该规则的限制条件时,电路会高亮、也可以导入上个项目使用的规则。
    4. 将元器件从原理图中导入PCB板,并进行模块化布局。
      区域化布局选择器件:Tools -> Cross Select Mode(交互选择)
    5. 布线,先连接近点复杂的线路,在连接远点的线路:先近后远
    6. 铺铜、补泪滴进一步保证PCB的质量
    7. 丝印层注释,丝印层可对部分模块进行详细说明,有利于开发者的调试与使用,防止过孔部分覆盖到丝印。


    注意事项

    原理图

    1. 芯片的选型:

       如电源芯片需注意:确定输入电压和输出电压的范围、工作温度、封装、转换效率…
       如A/D转换芯片:参考电压、转换位数和精度、供电电压、信噪比、采样速率、传输通道,接口类型、增益范围、工作温度、封装…
      

      大部分公司需要对新采购的芯片进行性能的评估,所以在选择的芯片符合生产要求内,还需考虑到该芯片是否与公司的已有的产品兼容等,这样还可以减少采购部分的工作量。

    选择电源芯片时,先需要对整个系统的功耗进行一个初步个估计,在根据其功耗大小,选择合适的芯片。

    1. 主控芯片旁,应该加一些 1uF 和100nF 的电容,作为旁路滤波作用
    2. 0Ω电阻又称跨接电阻器,是一种特殊用途的电阻。0Ω电阻并非真正的阻值为零,其电阻非常小。
      (用其代替跨线,连接印刷电路中不能连接的两点)
    3. 在一些低频信号线上,可以选择串联一些阻值非常小的电阻(0Ω),方便示波器检错时,引出该信号。
      此外,应该放置一定GND的测试点,方便示波器接地检测。
    4. 在高频信号,如时钟部分需要进行阻抗匹配(匹配该连接点导线的阻值)
      如:导线相当于理想电压源和内阻r,当 R=r 时,输出功率最大。
      在这里插入图片描述
      阻抗匹配作用:高速信号才需要考虑使用这样的电阻。
    • 因为信号源的阻抗很低,跟信号线之间阻抗不匹配,串上一个电阻后,可以改善匹配情况,以减少反射,避免振荡等。
    • 减少信号边沿的陡峭程度,从而减少高频噪声以及过冲等。因为串联的电阻,跟信号线的分布电容以及负载的输入电容等形成了一个RC电路,这样就会降低信号边沿的陡峭程度。如果一个信号的边沿非常陡峭,含有大量的高频成分,将会辐射干扰,另外,也容易产生过冲现象。
    1. 开关量信号的采集最好添加光电隔离部分。如果没有加光电耦合器,而是直接把开关量接到单片机上,可能会由于开关量输出的电压或电流超过单片机的正常工作电压从而对单片机造成损坏。
    2. 模拟量的采集: 信号输入 -> 信号调理 -> A/D转换 -> 数据传输

    信号调理:
      考虑到阻抗会对电路产生一定的影响,甚至可能会影响到信号采集电路的准确性,所以在电路中加入了两级跟随器来对电路进行阻抗变换,以提高电路的驱动能力和稳定性。此外A/D转换增益设置越大,其转换噪声也会被等效放大。

    1. 外部接口(USB、网口……)器件进行插拔时,可能产生静电损坏内部电路,所以需要考虑加低容值的ESD保护器进行静电释放(TVS管)。
    2. 实际应用中,经常需要自制接口和排线。如可以将UART、SPI、SDIO……用到的接口,通过排线或特殊的接口封装引到一块进行连接,大大优化PCB板与外界的连接。

    PCB

    1. 布局时:遵循模块化布局的规则
    • 开关电压电路部分中,电感和储能电解电容在布局时最好靠在一块。(按电源芯片数据手册来布局)
    • 各CPU的旁路电容靠近CPU摆放,不然不起作用
    • 不同电感应相隔一定距离,且垂直分布
    • 元器件的高度和位置是否会导致安装困难。
    1. 布线时:先重要后次之
    • 先布关键信号线(差分线和高速线),在布差分线时,需遵循差分线的走线规则,尽可能经过较少过孔次数。
    • 敏感的走线应该远离线圈/电感,虽然开关稳压器的线圈不是临界热回路的一部分,但不在线圈下方或靠近线圈处布敏感的控制走线,却是明智的选择。
    • 参考电压线的阻值应该竟可能小,保证参考电压线上不会存在较大的压降,影响精度。所以参考电压线可适当加粗变短
    • 因为主控芯片的引脚为复用引脚,所有可以适当替换引脚的功能,较少布线的交叉次数。
    1. 铺铜: 并不是铺铜越多,信号越好。
      如:为了防止串扰,变压器和网口连接器区域的地需要进行挖空处理。
    展开全文
  • 曾经在阿里碰到一道面试题 面试官问:APK打包流程是什么 ...APK加固的方案原理 APK加固总体架构 APK打包基本流程 Dex文件的意义 AES加密项目实战 APK加固项目实战 APK脱壳技术实战 一.APK...

    曾经在阿里碰到一道面试题
    面试官问:APK打包流程是什么

    (更多完整项目下载。未完待续。源码。图文知识后续上传github。)

    点击→关于我

    今天本文主要讲解:

    APK文件反编译

    • 什么是反编译
    • 如何防止反编译
    • APK文件的基本构造

    APK加固的方案原理

    • APK加固总体架构
    • APK打包基本流程
    • Dex文件的意义

    AES加密项目实战

    • APK加固项目实战
    • APK脱壳技术实战

    一.APK文件反编译

    1.什么是反编译
    • 定义:
      利用编译程序从源语言编写的源程序产生目标程序的过程
    2.怎么进行反编译?

    先了解apk的文件构造结构

    二.加固方案思想

    一个程序员的故事:

    辛辛苦苦找到一个对象,结婚后发现是个母夜叉。不给管钱就闹,晚上睡觉她趴着睡,导致这程序员无法去洗脚了。然而这个程序员很努力,平时除了上班,还能够做点外包,赚点外快。所以他就想到了把工资卡上交,而把赚到的外快放到了自己的小金库。从此过上了性福生活

    一个加密的故事:

    通过将非核心的dex文件进行暴露来达到保护核心dex文件的目的。

    三.基本原理

    3.1Apk打包流程

    加壳是在原来apk的基础上加一层保护壳,dex文件修改了就需要重新打包,否则apk安装不了。这就需要我们详细学习apk如何打包的

    3.2Dex文件是什么

    加固的目的是保护dex,直接而言就是对dex文件进行操作,对dex文件动刀子,必须知道dex文件是什么,能否直接动刀子

    3.3Dex文件加载流程

    加壳后的文件是不能直接用的,dex文件是加密的,所以我们需要对他进行解密,解密后的dex文件如何加载?

    3.4APK文件是怎么生产的

    image.png

    四.加固总体框架


    那么问题来了:

    • 如何达到加密效果?
    • 为什么是两个系列的dex?
    • 壳dex 怎么来的
    • 壳dex如何保护源dex?
    • 如何签名?
    • 如何运行新dex(如何脱壳)?
    4.1加密过程

    4.2APK文件如何签名


    4.3APK文件如何运行(脱壳)

    4.4如何制定某些类在 main dex中
    • multiDexKeepFile:手动加入要放到Main.dex中的类
    com.umeng.analytics.Abb.class
    
    • multiDexKeepProguard:以Proguard的方式手动加入要放到的Main.dex中的类
    -keep public class com.tencent.bugly.**{*;}
    

    总结

    完成APL加固,我们需要具备的知识体系有哪些

    (更多完整项目下载。未完待续。源码。图文知识后续上传github。)

    点击→关于我

    展开全文
  • 9.1.5 滚针轴承与带座外球面球轴承的参数化绘图流程图 9.1.6 关键技术 9.2 滚针轴承与带座外球面球轴承的选用与计算 9.2.1 滚针轴承与带座外球面球轴承的选用 9.2.2 滚针轴承与带座外球面球轴承选型参数计算 ...
  • 以上是CPU中央集中控制处理系统的主要工作过程,要全面具体实现上述工作过程,则要有软件支持,该软件程序流程图见图1—4。 图1-3 键盘功能框图 对图1-3所示的键盘功能作如下介绍: “时间”: 该键可设置系统...
  • 深入理解Android:卷II 邓凡平著 PDF扫描版

    千次下载 热门讨论 2014-10-10 20:56:40
    6.5.2 startService流程图 6.6 AMS中的进程管理 6.6.1 Linux进程管理介绍 6.6.2 关于Android中的进程管理的介绍 6.6.3 AMS进程管理函数分析 6.6.4 AMS进程管理总结 6.7 App的 Crash处理 6.7.1 应用进程的...
  • 2005-2009软件设计师历年真题

    千次下载 热门讨论 2010-05-18 19:20:10
     • 结构化设计方法和工具(系统流程图、HIPO图、控制流程图)  • 系统总体结构设计(总体布局、设计原则、模块结构设计、数据存储设计、系统配置方案)  • 系统详细设计(代码设计、数据库设计、用户界面设计...
  • 软件工程教程

    热门讨论 2012-07-06 23:10:29
    应用计算机科学、数学及管理科学等原理,以工程化的原则和方法来解决软件问题,指导计算机软件开发和维护的一门工程学科。  软件工程的原则 任务2 软件生命周期与软件开发模型 软件生命周期 软件开发模型 ...
  •  2.2.1 Windows操作系统总体架构  2.2.2 应用程序与Win32子系统  2.2.3 其他环境子系统  2.2.4 Native API  2.2.5 系统服务  2.2.6 执行程序组件  2.2.7 驱动程序  2.2.8 内核  2.2.9 硬件抽象层 ...
  • 软件设计师重点考点

    2013-12-08 22:26:39
    3.3系统总体结构设计 245 3.4系统详细设计 246 3.5系统设计说明书 247 4.系统实施知识 248 4.1系统实施的主要任务 248 4.2结构化程序设计、面向对象程序设计、可视化程序设计 248 4.3系统测试的目的、类型,系统测试...
  • 10.1.2 VFW采集开发流程图 373 10.2 使用VFW实现视频捕获和预览 373 10.2.1 建立单文档应用程序 373 10.2.2 创建视频窗口 375 10.2.3 设计回调函数 376 10.2.4 视频图像显示设置 378 10.2.5 捕获预览视频 379 ...
  • 精通ASP.NET3.5典型模块开发源代码

    热门讨论 2009-07-13 15:34:52
    11.1 缩略加水印的原理 128 11.2 自定义带版权水印的缩略 129 11.2.1 生成缩略功能 129 11.2.2 为缩略添加版权信息 131 11.2.3 为缩略添加水印 132 11.2.4 一个高质量的缩略水印模块 133 ...
  • 再有就是给做的朋友几点建议:尽量拿到现成的板子,尽量多搜集其他板子的全套资料,一定要拿到一张没问题的原理图。 网上流传的原理图多数是龚俊03年画的,再这里对龚俊表达一下我的敬意!!牛人! 但是那个有个小...
  • Keil C51 可以完成编辑、编译、连接、调试、仿真等整个开发流程。开发人 员可用IDE 本身或其它编辑器编辑C 或汇编源文件,然后分别有C51 及A51 编 辑器编译连接生成单片机可执行的二进制文件(.HEX),然后通过...
  • 除正常应免费赠送的软件与资料电子光盘外,另有自编译相关著名GIS软件二次开发手册、编译代码、相关项目开发文档、并提供可与Google earth数据交换的网上建筑三维建模共享软件、GIS软件在相关专业的中文操作使用手册...
  • 20_课堂答疑_函数调用流程入栈出栈过程 21_指针也是一种数据类型_基础 22_指针也是一种数据类型_强化_传智扫地僧 源码及文档 01_课程回顾 02_作业题强化和野指针 03_向null地址copy数据和不断改变指针指向强化 04_...
  • asp.net知识库

    2015-06-18 08:45:45
    Asp.net 2.0功能体验,总体设计思想 Asp.net 2.0 WebPart使用经验点滴 革新:.NET 2.0的自定义配置文件体系初探 关于如何在ASP.NET 2.0中定制Expression Builders 怎么在ASP.NET 2.0中使用Membership asp.net 2.0-...
  • C++网络爬虫项目

    2018-07-04 00:59:17
    网络爬虫的一般工作原理如下所示:  从互联网网页中选择部分网页的链接作为“种子URL”,放入“待抓取URL 队列”;  爬虫从“待抓取URL队列”中依次“读取URL”;  爬虫通过“DNS解析” 将读到的URL转换为...
  • ASP.NET 4高级程序设计(第4版)》【原版书为:Pro ASP.NET 4 in C# 2010】是ASP.NET领域的鸿篇巨制,全面讲解了ASP.NET4的各种特性及其背后的工作原理,并给出了许多针对如何构建复杂、可扩展的网站从实践中得出的...
  •  《asp.net 4高级程序设计:第4版》是asp.net 领域的鸿篇巨制,全面讲解了asp.net 4 的各种特性及其背后的工作原理,并给出了许多针对如何构建复杂、可扩展的网站从实践中得出的建议。本书还深入讲述了其他asp.net ...
  • JSP应用开发详解

    2013-04-20 15:45:57
    1.3.2 编译后的JSP 13 1.4 为什么使用JSP 16 1.5 开发第一个JSP页面 17 1.6 小结 19 第2章 搭建运行开发环境和集成开发环境 20 2.1 Eclipse的应用 20 2.1.1 Eclipse简述 20 2.1.2 安装Eclipse 21 2.1.3 更新Eclipse ...
  • 1.2.3编译命令的使用8 1.2.4解释执行命令的使用10 1.2.5UltraEdit的使用11 1.3一个简单的Java应用程序14 1.4一个简单的Java小程序16 1.5本章小结18 第2章Java语言基础19 2.1Java语言的特点19 2.2Java程序的...
  • 本步骤将编译 u-boot.bin文件,但此时还无法运行在FS2410开发板上。 二、修改 cpu/arm920t/start.S文件,完成 U-Boot的重定向 (1)修改中断禁止部分 # if defined(CONFIG_S3C2410) ldr r1, =0x7ff /*根据 2410...
  • ASP.NET精品课程+源代码

    千次下载 热门讨论 2009-01-05 20:15:51
    在案例实施前,要对ASP.NET语言的基础知识、基本理论、基本特征、语法基础、程序编译作一下简要的、系统的介绍。让学生对这门语言有一个总的认识和总的把握,以便与其他语言进行比较,对一些重要的概念像类、空间、...
  • [Oracle.11g权威指南(第2版)].谷长勇.扫描版.pdf

    千次下载 热门讨论 2013-06-23 21:16:09
    9.4.2 创建并编译PL/SQL过程 249 9.4.3 运行PL/SQL过程 254 9.4.4 调试PL/SQL过程 256 9.5 使用SQL Developer运行或创建报表 261 9.5.1 运行预定义报表 261 9.5.2 创建自定义报表 262 9.6 使用SQL Developer导出操作...

空空如也

空空如也

1 2
收藏数 25
精华内容 10
关键字:

编译原理总体流程图