1、写段小程序,然后用strace命令查看程序运行情况。

#include <stdio.h>int mian()
{printf("hello world.\n");return 0;
}
strace ./hello

从上图可以看出,会先调用execve系统函数对可执行程序进行解析。execve的使用方法见:执行新程序 execve()_杨博东的博客-CSDN博客_execve

2、execve系统函数处理流程

SYSCALL_DEFINE3(execve,const char __user *, filename,const char __user *const __user *, argv,const char __user *const __user *, envp)
{return do_execve(getname(filename), argv, envp);
}
do_execvedo_execveat_commonbprm_execveexec_binprmsearch_binary_handlerfmt->load_binary(bprm);

因为除了elf的可执行程序以外,还有shell脚本。不同的文件解析的方法不一样,下面以elf可执行性文件为例。

static struct linux_binfmt elf_format = {.module            = THIS_MODULE,.load_binary = load_elf_binary,.load_shlib      = load_elf_library,.core_dump      = elf_core_dump,.min_coredump  = ELF_EXEC_PAGESIZE,
};
static int load_elf_binary(struct linux_binprm *bprm)
{struct file *interpreter = NULL; /* to shut gcc up */unsigned long load_addr = 0, load_bias = 0;int load_addr_set = 0;unsigned long error;struct elf_phdr *elf_ppnt, *elf_phdata, *interp_elf_phdata = NULL;struct elf_phdr *elf_property_phdata = NULL;unsigned long elf_bss, elf_brk;int bss_prot = 0;int retval, i;unsigned long elf_entry;unsigned long e_entry;unsigned long interp_load_addr = 0;unsigned long start_code, end_code, start_data, end_data;unsigned long reloc_func_desc __maybe_unused = 0;int executable_stack = EXSTACK_DEFAULT;struct elfhdr *elf_ex = (struct elfhdr *)bprm->buf;struct elfhdr *interp_elf_ex = NULL;struct arch_elf_state arch_state = INIT_ARCH_ELF_STATE;struct mm_struct *mm;struct pt_regs *regs;retval = -ENOEXEC;/* First of all, some simple consistency checks *//* 1、检查目标程序elf头部,前128字节。比较MAGIC("\177ELF") */if (memcmp(elf_ex->e_ident, ELFMAG, SELFMAG) != 0)   goto out;/* ET_EXEC可执行文件;ET_DYN动态库 */if (elf_ex->e_type != ET_EXEC && elf_ex->e_type != ET_DYN)    goto out;if (!elf_check_arch(elf_ex))goto out;if (elf_check_fdpic(elf_ex))goto out;if (!bprm->file->f_op->mmap)goto out;/* 2、加载目标程序的程序头表 */elf_phdata = load_elf_phdrs(elf_ex, bprm->file);              if (!elf_phdata)goto out;elf_ppnt = elf_phdata;/* 3、如需动态链接,寻找和处理解析器   */for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++) {char *elf_interpreter;if (elf_ppnt->p_type == PT_GNU_PROPERTY) {elf_property_phdata = elf_ppnt;continue;}/* 检查是否有需要加载的解析器 */if (elf_ppnt->p_type != PT_INTERP)continue;/** This is the program interpreter used for shared libraries -* for now assume that this is an a.out format binary.*/retval = -ENOEXEC;if (elf_ppnt->p_filesz > PATH_MAX || elf_ppnt->p_filesz < 2)goto out_free_ph;retval = -ENOMEM;elf_interpreter = kmalloc(elf_ppnt->p_filesz, GFP_KERNEL);if (!elf_interpreter)goto out_free_ph;/* 解析字段,读取路径内容 */retval = elf_read(bprm->file, elf_interpreter, elf_ppnt->p_filesz,elf_ppnt->p_offset);if (retval < 0)goto out_free_interp;/* make sure path is NULL terminated */retval = -ENOEXEC;if (elf_interpreter[elf_ppnt->p_filesz - 1] != '\0')goto out_free_interp;/* 打开解析器 */interpreter = open_exec(elf_interpreter);kfree(elf_interpreter);retval = PTR_ERR(interpreter);if (IS_ERR(interpreter))goto out_free_ph;/** If the binary is not readable then enforce mm->dumpable = 0* regardless of the interpreter's permissions.*/would_dump(bprm, interpreter);interp_elf_ex = kmalloc(sizeof(*interp_elf_ex), GFP_KERNEL);if (!interp_elf_ex) {retval = -ENOMEM;goto out_free_ph;}/* Get the exec headers *//* 4、读取解析器程序表头 */retval = elf_read(interpreter, interp_elf_ex,sizeof(*interp_elf_ex), 0);if (retval < 0)goto out_free_dentry;break;out_free_interp:kfree(elf_interpreter);goto out_free_ph;}elf_ppnt = elf_phdata;for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++)switch (elf_ppnt->p_type) {case PT_GNU_STACK:if (elf_ppnt->p_flags & PF_X)               /* 检测栈区是否可执行 gcc -z execstack/noexecstack */executable_stack = EXSTACK_ENABLE_X;elseexecutable_stack = EXSTACK_DISABLE_X;break;case PT_LOPROC ... PT_HIPROC:retval = arch_elf_pt_proc(elf_ex, elf_ppnt,bprm->file, false,&arch_state);if (retval)goto out_free_dentry;break;}/* Some simple consistency checks for the interpreter *//* 检测解析器头信息 */if (interpreter) {retval = -ELIBBAD;/* Not an ELF interpreter */if (memcmp(interp_elf_ex->e_ident, ELFMAG, SELFMAG) != 0)goto out_free_dentry;/* Verify the interpreter has a valid arch */if (!elf_check_arch(interp_elf_ex) ||elf_check_fdpic(interp_elf_ex))goto out_free_dentry;/* Load the interpreter program headers */interp_elf_phdata = load_elf_phdrs(interp_elf_ex,interpreter);if (!interp_elf_phdata)goto out_free_dentry;/* Pass PT_LOPROC..PT_HIPROC headers to arch code */elf_property_phdata = NULL;elf_ppnt = interp_elf_phdata;for (i = 0; i < interp_elf_ex->e_phnum; i++, elf_ppnt++)switch (elf_ppnt->p_type) {case PT_GNU_PROPERTY:elf_property_phdata = elf_ppnt;break;case PT_LOPROC ... PT_HIPROC:retval = arch_elf_pt_proc(interp_elf_ex,elf_ppnt, interpreter,true, &arch_state);if (retval)goto out_free_dentry;break;}}retval = parse_elf_properties(interpreter ?: bprm->file,elf_property_phdata, &arch_state);if (retval)goto out_free_dentry;/** Allow arch code to reject the ELF at this point, whilst it's* still possible to return an error to the code that invoked* the exec syscall.*/retval = arch_check_elf(elf_ex,!!interpreter, interp_elf_ex,&arch_state);if (retval)goto out_free_dentry;/* Flush all traces of the currently running executable */retval = begin_new_exec(bprm);if (retval)goto out_free_dentry;/* Do this immediately, since STACK_TOP as used in setup_arg_pagesmay depend on the personality.  */SET_PERSONALITY2(*elf_ex, &arch_state);if (elf_read_implies_exec(*elf_ex, executable_stack))current->personality |= READ_IMPLIES_EXEC;if (!(current->personality & ADDR_NO_RANDOMIZE) && randomize_va_space)current->flags |= PF_RANDOMIZE;setup_new_exec(bprm);/* Do this so that we can load the interpreter, if need be.  We willchange some of these later *//* 解析器执行需要内核空间 */retval = setup_arg_pages(bprm, randomize_stack_top(STACK_TOP),executable_stack);if (retval < 0)goto out_free_dentry;elf_bss = 0;elf_brk = 0;start_code = ~0UL;end_code = 0;start_data = 0;end_data = 0;/* Now we do a little grungy work by mmapping the ELF image intothe correct location in memory. *//* 5、装入目标程序的segment */for(i = 0, elf_ppnt = elf_phdata;i < elf_ex->e_phnum; i++, elf_ppnt++) {int elf_prot, elf_flags;unsigned long k, vaddr;unsigned long total_size = 0;unsigned long alignment;/* LOAD类型 */if (elf_ppnt->p_type != PT_LOAD)continue;if (unlikely (elf_brk > elf_bss)) {unsigned long nbyte;/* There was a PT_LOAD segment with p_memsz > p_fileszbefore this one. Map anonymous pages, if needed,and clear the area.  */retval = set_brk(elf_bss + load_bias,elf_brk + load_bias,bss_prot);if (retval)goto out_free_dentry;nbyte = ELF_PAGEOFFSET(elf_bss);if (nbyte) {nbyte = ELF_MIN_ALIGN - nbyte;if (nbyte > elf_brk - elf_bss)nbyte = elf_brk - elf_bss;if (clear_user((void __user *)elf_bss +load_bias, nbyte)) {/** This bss-zeroing can fail if the ELF* file specifies odd protections. So* we don't check the return value*/}}}elf_prot = make_prot(elf_ppnt->p_flags, &arch_state,!!interpreter, false);elf_flags = MAP_PRIVATE;vaddr = elf_ppnt->p_vaddr;/** The first time through the loop, load_addr_set is false:* layout will be calculated. Once set, use MAP_FIXED since* we know we've already safely mapped the entire region with* MAP_FIXED_NOREPLACE in the once-per-binary logic following.*/if (load_addr_set) {elf_flags |= MAP_FIXED;} else if (elf_ex->e_type == ET_EXEC) {/** This logic is run once for the first LOAD Program* Header for ET_EXEC binaries. No special handling* is needed.*/elf_flags |= MAP_FIXED_NOREPLACE;} else if (elf_ex->e_type == ET_DYN) {/** This logic is run once for the first LOAD Program* Header for ET_DYN binaries to calculate the* randomization (load_bias) for all the LOAD* Program Headers.** There are effectively two types of ET_DYN* binaries: programs (i.e. PIE: ET_DYN with INTERP)* and loaders (ET_DYN without INTERP, since they* _are_ the ELF interpreter). The loaders must* be loaded away from programs since the program* may otherwise collide with the loader (especially* for ET_EXEC which does not have a randomized* position). For example to handle invocations of* "./ld.so someprog" to test out a new version of* the loader, the subsequent program that the* loader loads must avoid the loader itself, so* they cannot share the same load range. Sufficient* room for the brk must be allocated with the* loader as well, since brk must be available with* the loader.** Therefore, programs are loaded offset from* ELF_ET_DYN_BASE and loaders are loaded into the* independently randomized mmap region (0 load_bias* without MAP_FIXED nor MAP_FIXED_NOREPLACE).*/if (interpreter) {/* 使用解析器的程序基地址 */load_bias = ELF_ET_DYN_BASE;if (current->flags & PF_RANDOMIZE)       /* 基地址随机,避免入侵 */load_bias += arch_mmap_rnd();alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);if (alignment)load_bias &= ~(alignment - 1);elf_flags |= MAP_FIXED_NOREPLACE;} elseload_bias = 0;/** Since load_bias is used for all subsequent loading* calculations, we must lower it by the first vaddr* so that the remaining calculations based on the* ELF vaddrs will be correctly offset. The result* is then page aligned.*/load_bias = ELF_PAGESTART(load_bias - vaddr);}/** Calculate the entire size of the ELF mapping (total_size).* (Note that load_addr_set is set to true later once the* initial mapping is performed.)*/if (!load_addr_set) {total_size = total_mapping_size(elf_phdata,elf_ex->e_phnum);if (!total_size) {retval = -EINVAL;goto out_free_dentry;}}/* 映射文件某段区间与用户空间虚拟地址映射关系 */error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt,elf_prot, elf_flags, total_size);if (BAD_ADDR(error)) {retval = IS_ERR((void *)error) ?PTR_ERR((void*)error) : -EINVAL;goto out_free_dentry;}if (!load_addr_set) {load_addr_set = 1;load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);if (elf_ex->e_type == ET_DYN) {load_bias += error -ELF_PAGESTART(load_bias + vaddr);load_addr += load_bias;reloc_func_desc = load_bias;}}/* 计算数据段地址 */k = elf_ppnt->p_vaddr;if ((elf_ppnt->p_flags & PF_X) && k < start_code)start_code = k;if (start_data < k)start_data = k;/** Check to see if the section's size will overflow the* allowed task size. Note that p_filesz must always be* <= p_memsz so it is only necessary to check p_memsz.*/if (BAD_ADDR(k) || elf_ppnt->p_filesz > elf_ppnt->p_memsz ||elf_ppnt->p_memsz > TASK_SIZE ||TASK_SIZE - elf_ppnt->p_memsz < k) {/* set_brk can never work. Avoid overflows. */retval = -EINVAL;goto out_free_dentry;}k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;if (k > elf_bss)elf_bss = k;if ((elf_ppnt->p_flags & PF_X) && end_code < k)end_code = k;if (end_data < k)end_data = k;k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;if (k > elf_brk) {bss_prot = elf_prot;elf_brk = k;}}e_entry = elf_ex->e_entry + load_bias;elf_bss += load_bias;elf_brk += load_bias;start_code += load_bias;end_code += load_bias;start_data += load_bias;end_data += load_bias;/* Calling set_brk effectively mmaps the pages that we need* for the bss and break sections.  We must do this before* mapping in the interpreter, to make sure it doesn't wind* up getting placed where the bss needs to go.*/retval = set_brk(elf_bss, elf_brk, bss_prot);if (retval)goto out_free_dentry;if (likely(elf_bss != elf_brk) && unlikely(padzero(elf_bss))) {retval = -EFAULT; /* Nobody gets to see this, but.. */goto out_free_dentry;}/* 6、填充程序的入口地址 */if (interpreter) {/* 需要动态库链接场景。装入解析器映像。 */elf_entry = load_elf_interp(interp_elf_ex,interpreter,load_bias, interp_elf_phdata,&arch_state);if (!IS_ERR((void *)elf_entry)) {/** load_elf_interp() returns relocation* adjustment*/interp_load_addr = elf_entry;elf_entry += interp_elf_ex->e_entry;    /* 入口地址是load_elf_interp返回值,解析器映像的入口地址 */}if (BAD_ADDR(elf_entry)) {retval = IS_ERR((void *)elf_entry) ?(int)elf_entry : -EINVAL;goto out_free_dentry;}reloc_func_desc = interp_load_addr;allow_write_access(interpreter);fput(interpreter);kfree(interp_elf_ex);kfree(interp_elf_phdata);} else {/* 映射文件本身入口地址 */elf_entry = e_entry;if (BAD_ADDR(elf_entry)) {retval = -EINVAL;goto out_free_dentry;}}kfree(elf_phdata);set_binfmt(&elf_format);#ifdef ARCH_HAS_SETUP_ADDITIONAL_PAGESretval = ARCH_SETUP_ADDITIONAL_PAGES(bprm, elf_ex, !!interpreter);if (retval < 0)goto out;
#endif /* ARCH_HAS_SETUP_ADDITIONAL_PAGES *//* 7、填写目标程序参数环境变量等信息,生成elf映射表 */retval = create_elf_tables(bprm, elf_ex,load_addr, interp_load_addr, e_entry);if (retval < 0)goto out;/* 填充mm_struct结构体 */mm = current->mm;mm->end_code = end_code;mm->start_code = start_code;mm->start_data = start_data;mm->end_data = end_data;mm->start_stack = bprm->p;if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {/** For architectures with ELF randomization, when executing* a loader directly (i.e. no interpreter listed in ELF* headers), move the brk area out of the mmap region* (since it grows up, and may collide early with the stack* growing down), and into the unused ELF_ET_DYN_BASE region.*/if (IS_ENABLED(CONFIG_ARCH_HAS_ELF_RANDOMIZE) &&elf_ex->e_type == ET_DYN && !interpreter) {mm->brk = mm->start_brk = ELF_ET_DYN_BASE;}mm->brk = mm->start_brk = arch_randomize_brk(mm);
#ifdef compat_brk_randomizedcurrent->brk_randomized = 1;
#endif}if (current->personality & MMAP_PAGE_ZERO) {/* Why this, you ask???  Well SVr4 maps page 0 as read-only,and some applications "depend" upon this behavior.Since we do not have the power to recompile these, weemulate the SVr4 behavior. Sigh. */error = vm_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC,MAP_FIXED | MAP_PRIVATE, 0);}regs = current_pt_regs();
#ifdef ELF_PLAT_INIT/** The ABI may specify that certain registers be set up in special* ways (on i386 %edx is the address of a DT_FINI function, for* example.  In addition, it may also specify (eg, PowerPC64 ELF)* that the e_entry field is the address of the function descriptor* for the startup routine, rather than the address of the startup* routine itself.  This macro performs whatever initialization to* the regs structure is required as well as any relocations to the* function descriptor entries when executing dynamically links apps.*/ELF_PLAT_INIT(regs, reloc_func_desc);
#endiffinalize_exec(bprm);/* 8、进入新程序入口,修改CPU寄存器值。返回用户空间时进入新的程序入口 */START_THREAD(elf_ex, regs, elf_entry, bprm->p);retval = 0;
out:return retval;/* error cleanup */
out_free_dentry:kfree(interp_elf_ex);kfree(interp_elf_phdata);allow_write_access(interpreter);if (interpreter)fput(interpreter);
out_free_ph:kfree(elf_phdata);goto out;
}

1、解析程序elf头。readelf -h hello

使用UE打开目标程序文件,二进制显示。

2、通过load_elf_phdrs加载目标程序的程序头表。readelf -l hello

装载程序的时候,先解析程序头表(program headers)。 gcc在编译时,除非显示的使用-static标签,否则所有程序的链接都是动态链接的,也就是说需要解释器。

3、处理解析器段。readelf -l hello   &&  ldd hello

解析字段,查找到解析器的路径

4、读取并检查解析器的程序头表

根据解析器的路径打开解析器,检测解析器文件

5、装载目标程序的segment。readelf -l hello

在链接阶段,链接器以section为单位,但是在装载程序的时候以segment为单位。segment中包含多个同一属性(rwxp)的section,更加合理使用内存空间。

6、填入程序的入口地址

if (interpreter) {load_bias = ELF_ET_DYN_BASE;if (current->flags & PF_RANDOMIZE)load_bias += arch_mmap_rnd();alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);if (alignment)load_bias &= ~(alignment - 1);elf_flags |= MAP_FIXED_NOREPLACE;
} elseload_bias = 0;

避免程序的地址被猜测,可以根据PF_RANDOMIZE标志,获取一个随机的基地址load_bias。

7、装载目标程序的参数,环境变量等必要信息。如:argc,argv,envc

start_code += load_bias;
end_code += load_bias;
start_data += load_bias;
end_data += load_bias;
mm = current->mm;
mm->end_code = end_code;
mm->start_code = start_code;
mm->start_data = start_data;
mm->end_data = end_data;
mm->start_stack = bprm->p;

8、准备进入新程序入口

START_THREAD(elf_ex, regs, elf_entry, bprm->p);

其中START_THREAD宏定义如下,函数的入口地址elf_entry变量就是start_thread的第二个入参pc

#define start_thread(regs,pc,sp)                 \
({                                  \unsigned long r7, r8, r9;                  \\if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC)) {            \r7 = regs->ARM_r7;                 \r8 = regs->ARM_r8;                 \r9 = regs->ARM_r9;                 \}                              \memset(regs->uregs, 0, sizeof(regs->uregs));         \if (IS_ENABLED(CONFIG_BINFMT_ELF_FDPIC) &&         \current->personality & FDPIC_FUNCPTRS) {            \regs->ARM_r7 = r7;                 \regs->ARM_r8 = r8;                 \regs->ARM_r9 = r9;                 \regs->ARM_r10 = current->mm->start_data;     \} else if (!IS_ENABLED(CONFIG_MMU))                \regs->ARM_r10 = current->mm->start_data;     \if (current->personality & ADDR_LIMIT_32BIT)            \regs->ARM_cpsr = USR_MODE;             \else                               \regs->ARM_cpsr = USR26_MODE;               \if (elf_hwcap & HWCAP_THUMB && pc & 1)             \regs->ARM_cpsr |= PSR_T_BIT;               \regs->ARM_cpsr |= PSR_ENDSTATE;                    \regs->ARM_pc = pc & ~1;        /* pc */            \regs->ARM_sp = sp;     /* sp */            \
})

返回用户空间时就进入新程序的入口地址,执行新程序

程序是如何跑起来的?相关推荐

  1. c语言小程序跑马灯,微信小程序实现文字跑马灯效果

    本文实例为大家分享了微信小程序实现文字跑马灯的具体代码,供大家参考,具体内容如下 wxml 1 显示完后再显示 Box"> 2 出现白边后即显示 Box"> {{tex ...

  2. 读《程序是怎样跑起来的》第一章有感

    程序是怎样跑起来的,一听名字就特别吸引人注意,想要翻开书一探究竟. 首先,我学习了这本书的第一章,"对程序员来说CPU是什么",刚一看到这个标题,我只知道CPU是计算机的运算核心和 ...

  3. 《程序是怎样跑起来的》(上)

    学习笔记 此书前言 无论任何事情,了解其本质非常重要.只有了解了本质才能提高利用效率.这样一来,即使有新技术出现,也能很容易的理解并掌握. 第1章 对程序员来说CPU是什么 本章提问 程序是什么? 程 ...

  4. 《程序是怎样跑起来的》第一章有感

    在看完<程序是怎样跑起来的>第一章后,我开始明白程序是怎么运行的,该书介绍了程序是什么.由什么组成的以及是内存的机制.读完这本书的第一章后,我知道程序是指令与数据的组合.不过在刚学习的时候 ...

  5. 《程序是怎样跑起来的》读书笔记

    2017-2-25 前段时间读完<程序是怎样跑起来的>,对程序的运行过程认识更深.书中前六章讲解了CPU.二进制.内存与磁盘.数据压缩,比较易于理解,读过之后也收益良多.后面章节涉及汇编语 ...

  6. LabVIEW用了多线程,程序是不是会跑的更快些

    LabVIEW用了多线程,程序是不是会跑的更快些 这个取决于具体的应用程序.如果应用程序中的任务顺序执行,不会看到任何改善.比方说,程序打开文件,从文件中读取数据,然后关闭文件.多线程并不能使的应用程 ...

  7. 程序是怎么跑起来的——虚拟内存与动态链接

    0.前言 计算机的核心任务就是运行程序,而程序是如何运行的?这个问题一直困扰我很多年.网上有很多资料介绍程序如何被编译,如何被链接,然后装载,最后到OS中运行的,但都很分散,讲到的都是点,很少有串起来 ...

  8. 《程序是怎样跑起来的》(上、中、下)

    计算机基础原理(包含程序是怎样跑起来的上中下) https://www.cnblogs.com/xmusxy/category/1469722.html <程序是怎样跑起来的>(上) 学习 ...

  9. 《程序是怎样跑起来的》矢泽久雄[日] - 读书笔记

    <程序是怎样跑起来的>矢泽久雄[日]  读书笔记,详细建议阅读原版图书学习 一 CPU是什么 1 程序由指令与数据组成,是指示计算机每一步动作的一组指令,机器语言指的是CPU可以直接识别并 ...

  10. 网易云引领程序员健康跑,Running Coder火爆IT界

    12月10日,由网易云举办的Running Coder程序员联跑活动成功举办,本次活动总共吸引了193名程序员到场,其中还有铁人三项记录保持者.108公里越野马拉松长跑选手现场助阵,气氛火爆,参与跑步 ...

最新文章

  1. Linux 常用技巧记录
  2. 数据中心分解实验四--PC和VPC
  3. lucene .doc文件格式解析——见图
  4. 阅读笔记1(面试题功能测试-自动化提升效率)
  5. 使用CSS3各个属性实现小人的动画
  6. VTK:PolyData之ImplicitDataSetClipping
  7. linux 命令 nohup 后台运行
  8. C# 如何跨平台调用C++的函数指针!
  9. 开发经验分享_01_遇到问题三步走(思路+实战)
  10. 【OpenCV 例程200篇】20. 图像的按位运算
  11. 鸿蒙HI3516-HAP的编译-2021426
  12. 各位学Python的要小心了!!!
  13. assign ur here php,ecshop源码分析01
  14. 嵌入式Ubuntu 搭建caffee环境
  15. AndroidStudio配置一键360加固gradle脚本
  16. 计算机专业可以从事平面设计吗,计算机专业和平面设计专业是一个专业不?
  17. 捷达vs7测试_抢先测试捷达VS7!你期待吗
  18. linux条件变量cond,Linux C 条件变量cond的使用记录
  19. mvc中viewdata 和viewbag的区别
  20. java将图片存储在数据库(mysql)

热门文章

  1. XPS表征(工作原理与特点)
  2. ANSYS分析谐振激励下压头的破岩机理
  3. P1118 [USACO06FEB]数字三角形`Backward Digit Su`… 回溯法
  4. 【附源码】计算机毕业设计SSM泰兴市公交信息系统
  5. 华铭智能属于芯片概念吗_绩优滞涨的科技股名单来了!这类千亿概念股业绩翻10倍,两大活跃资金加仓股仅6只,射频芯片龙头在列...
  6. matlab相机标定程序,MATLAB单相机校准程序中文.pdf
  7. 赛迪智库:世界经济论坛发布报告,四大因素护航物联网产业发展
  8. html的JEE安装,JEE ( Java 企业级开发技术)
  9. 【开源】自制简易示波器V1.0
  10. 使用m2e将工程转化为maven工程后eclipse报Plugin execution not covered by lifecycle configuration:xxx plugin问题的解决方法