_dl_start源码分析_ld.so link_map-程序员宅基地

技术标签: linux逆向编程  

_dl_start源码分析

上一章分析了linux系统调用sys_execv的源码,最终linux会将控制权交给解释器的_start函数,该函数紧接着调用_dl_start函数,本章就分析该函数。因为glibc内部的宏定义太多,不适合代码的阅读,因此本章以及后面的章节删除了一部分定义和代码分支。

elf/rtld.c
_dl_start第一部分

static ElfW(Addr) __attribute_used__ internal_function
_dl_start (void *arg)
{
# define bootstrap_map GL(dl_rtld_map)

#define RTLD_BOOTSTRAP
#define RESOLVE_MAP(sym, version, flags) (&bootstrap_map)
#include "dynamic-link.h"

  if (HP_TIMING_INLINE && HP_TIMING_AVAIL)
    HP_TIMING_NOW (start_time);

GL的宏定义就是在变量名前面加上下划线,例如GL(dl_rtld_map)也即_dl_rtld_map,其为link_map结构。
HP_TIMING_NOW内部使用rdtsc指令获得cpu的计数器值,计数器一共64位,高位在rdx寄存器中,低位在rax寄存器中。

elf/rtld.c
_dl_start第二部分

  bootstrap_map.l_addr = elf_machine_load_address ();
  bootstrap_map.l_ld = (void *) bootstrap_map.l_addr + elf_machine_dynamic ();
  elf_get_dynamic_info (&bootstrap_map, NULL);

#if NO_TLS_OFFSET != 0
  bootstrap_map.l_tls_offset = NO_TLS_OFFSET;
#endif

elf_machine_load_address获得解释器的装载地址,elf_machine_dynamic获得.dynamic节的静态地址,加上装载地址即是.dynamic节的实际地址。
elf_get_dynamic_info设置info,后面的链接过程要大量用到这个数组的信息。

sysdeps/x86_64/dl-machine.h
_dl_start->elf_machine_load_address

static inline Elf64_Addr __attribute__ ((unused))
elf_machine_load_address (void)
{
  Elf64_Addr addr;
  asm ("leaq _dl_start(%%rip), %0\n\t"
       "subq 1f(%%rip), %0\n\t"
       ".section\t.data.rel.ro\n"
       "1:\t.quad _dl_start\n\t"
       ".previous\n\t"
       : "=r" (addr) : : "cc");

  return addr;
}

data段中存储了_dl_start静态连接后的地址,汇编的第一句获取了_dl_start当前运行时的实际地址,两者的差即是解释器的装载地址addr。

sysdeps/x86_64/dl-machine.h
_dl_start->elf_machine_dynamic

static inline Elf64_Addr __attribute__ ((unused))
elf_machine_dynamic (void)
{
  Elf64_Addr addr;
  addr = (Elf64_Addr) &_DYNAMIC;
  return addr;
}

elf_machine_dynamic返回.dynamic节的地址,因为.dynamic节的地址是GOT表中的第一个项,所以addr也即GOT表的地址。

sysdeps/x86_64/dl-machine.h
_dl_start->elf_machine_dynamic

inline void __attribute__ ((unused, always_inline))
elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
{
  ElfW(Dyn) *dyn = l->l_ld;
  ElfW(Dyn) **info;
  typedef Elf64_Xword d_tag_utype;
  info = l->l_info;

  while (dyn->d_tag != DT_NULL)
    {
      if ((d_tag_utype) dyn->d_tag < DT_NUM)
    info[dyn->d_tag] = dyn;
      else if (dyn->d_tag >= DT_LOPROC &&
           dyn->d_tag < DT_LOPROC + DT_THISPROCNUM)
    info[dyn->d_tag - DT_LOPROC + DT_NUM] = dyn;
      else if ((d_tag_utype) DT_VERSIONTAGIDX (dyn->d_tag) < DT_VERSIONTAGNUM)
    info[VERSYMIDX (dyn->d_tag)] = dyn;
      else if ((d_tag_utype) DT_EXTRATAGIDX (dyn->d_tag) < DT_EXTRANUM)
    info[DT_EXTRATAGIDX (dyn->d_tag) + DT_NUM + DT_THISPROCNUM
         + DT_VERSIONTAGNUM] = dyn;
      else if ((d_tag_utype) DT_VALTAGIDX (dyn->d_tag) < DT_VALNUM)
    info[DT_VALTAGIDX (dyn->d_tag) + DT_NUM + DT_THISPROCNUM
         + DT_VERSIONTAGNUM + DT_EXTRANUM] = dyn;
      else if ((d_tag_utype) DT_ADDRTAGIDX (dyn->d_tag) < DT_ADDRNUM)
    info[DT_ADDRTAGIDX (dyn->d_tag) + DT_NUM + DT_THISPROCNUM
         + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM] = dyn;
      ++dyn;
    }

  ADJUST_DYN_INFO (DT_HASH);
  ADJUST_DYN_INFO (DT_PLTGOT);
  ADJUST_DYN_INFO (DT_STRTAB);
  ADJUST_DYN_INFO (DT_SYMTAB);
  ADJUST_DYN_INFO (DT_RELA);
  ADJUST_DYN_INFO (DT_JMPREL);
  ADJUST_DYN_INFO (VERSYMIDX (DT_VERSYM));
  ADJUST_DYN_INFO (DT_ADDRTAGIDX (DT_GNU_HASH) + DT_NUM + DT_THISPROCNUM
               + DT_VERSIONTAGNUM + DT_EXTRANUM + DT_VALNUM);
}

ElfW的宏定义为

#define ElfW(type)  _ElfW (Elf, __ELF_NATIVE_CLASS, type)
#define _ElfW(e,w,t)    _ElfW_1 (e, w, _##t)
#define _ElfW_1(e,w,t)  e##w##t

例如上面代码中的ElfW(Addr)其实就是Elf64_Addr(如果是32位机则是Elf32_Addr)。
因此ElfW(Dyn)拓展开来就是Elf64_Dyn,其定义如下,

typedef struct
{
  Elf64_Sxword  d_tag;
  union
    {
      Elf64_Xword d_val;
      Elf64_Addr d_ptr;
    } d_un;
} Elf64_Dyn;

d_tag内存储的是.dynamic节中每个值的类型,d_val和d_ptr则是其值或地址。
elf_get_dynamic_info函数首先遍历.dynamic节中所有的值,根据d_tag类型将不同的值存入link_map的info数组中。
接下来通过ADJUST_DYN_INFO宏调整.dynamic节中对应的地址,例如.hash、.plt.got、.symtab对应的section的地址。
ADJUST_DYN_INFO宏定义定义在函数内部,

# define ADJUST_DYN_INFO(tag) 
      do                                      
    if (info[tag] != NULL)                            
      {                                                           
          info[tag]->d_un.d_ptr += l_addr;                    
      }                                   
      while (0)

这里就是简单加上解释器的实际装载地址。

elf/rtld.c
_dl_start第三部分

  if (bootstrap_map.l_addr || ! bootstrap_map.l_info[VALIDX(DT_GNU_PRELINKED)])
    {
      ELF_DYNAMIC_RELOCATE (&bootstrap_map, 0, 0, 0);
    }
  bootstrap_map.l_relocated = 1;

  ElfW(Addr) entry = _dl_start_final (arg);

# define ELF_MACHINE_START_ADDRESS(map, start) (start)
  return ELF_MACHINE_START_ADDRESS (GL(dl_ns)[LM_ID_BASE]._ns_loaded, entry);

}

ELF_DYNAMIC_RELOCATE宏定义用来对ld.so自身进行重定位,主要是修改.rela.dyn和.rela.plt中的各个地址值。这就是鸡生蛋,蛋生鸡的问题,ld.so需要自己给自己重定位。然后将l_relocated设置为1表示重定位完成。后面就可以访问全局变量和函数了。

elf/dynamic-link.h
_dl_start->ELF_DYNAMIC_RELOCATE

# define ELF_DYNAMIC_RELOCATE(map, lazy, consider_profile, skip_ifunc) \
  do {                                        \
    int edr_lazy = elf_machine_runtime_setup ((map), (lazy),              \
                          (consider_profile));        \
    ELF_DYNAMIC_DO_REL ((map), edr_lazy, skip_ifunc);                 \
    ELF_DYNAMIC_DO_RELA ((map), edr_lazy, skip_ifunc);                \
  } while (0)

elf_machine_runtime_setup什么也不做,假设ELF_DYNAMIC_DO_REL为空定义,因此下面只看ELF_DYNAMIC_DO_RELA。

elf/dynamic-link.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC

#  define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, do_lazy, skip_ifunc, test_rel) 
  do {                                        
    struct { ElfW(Addr) start, size;                          
         __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative; int lazy; }  
      ranges[2] = { { 0, 0, 0, 0 }, { 0, 0, 0, 0 } };                 

    if ((map)->l_info[DT_##RELOC])                        
      {                                       
    ranges[0].start = D_PTR ((map), l_info[DT_##RELOC]);              
    ranges[0].size = (map)->l_info[DT_##RELOC##SZ]->d_un.d_val;       
    if (map->l_info[VERSYMIDX (DT_##RELOC##COUNT)] != NULL)           
      ranges[0].nrelative                             
        = MIN (map->l_info[VERSYMIDX (DT_##RELOC##COUNT)]->d_un.d_val,    
           ranges[0].size / sizeof (ElfW(reloc)));            
      }                                       
    if ((map)->l_info[DT_PLTREL]                          
    && (!test_rel || (map)->l_info[DT_PLTREL]->d_un.d_val == DT_##RELOC)) 
      {                                       
        ElfW(Addr) start = D_PTR ((map), l_info[DT_JMPREL]);                                              
        ranges[0].size += (map)->l_info[DT_PLTRELSZ]->d_un.d_val;                             
      }                                       
      elf_dynamic_do_##reloc ((map), ranges[0].start, ranges[0].size,         
                  ranges[0].nrelative, 0, skip_ifunc);                                   
  } while (0)

_ELF_DYNAMIC_DO_RELOC宏定义首先根据.dynamic节中的信息,获取.rel.dyn节的起始地址存放在start中,再获取.rel.dyn的大小存放在size中,然后获取.rel.dyn中类型为R_X86_64_RELATIVE的大小存放在nrelative中,因为.rel.dyn和.rel.plt节是紧挨着的,再往下获取.rel.plt节的大小,累加在size中,根据这些信息,最后调用elf_dynamic_do_Rel函数执行重定位。

elf/do-rel.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC->elf_dynamic_do_Rel

auto inline void __attribute__ ((always_inline))
elf_dynamic_do_Rel (struct link_map *map,
            ElfW(Addr) reladdr, ElfW(Addr) relsize,
            __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative,
            int lazy, int skip_ifunc)
{
  const ElfW(Rel) *r = (const void *) reladdr;
  const ElfW(Rel) *end = (const void *) (reladdr + relsize);
  ElfW(Addr) l_addr = map->l_addr;

  const ElfW(Sym) *const symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]);
  const ElfW(Rel) *relative = r;
  r += nrelative;

  for (; relative < r; ++relative)
    DO_ELF_MACHINE_REL_RELATIVE (map, l_addr, relative);

  const ElfW(Half) *const version = (const void *) D_PTR (map, l_info[VERSYMIDX (DT_VERSYM)]);

  for (; r < end; ++r)
  {
    ElfW(Half) ndx = version[ELFW(R_SYM) (r->r_info)] & 0x7fff;
    elf_machine_rel (map, r, &symtab[ELFW(R_SYM) (r->r_info)], &map->l_versions[ndx],
                   (void *) (l_addr + r->r_offset), skip_ifunc);
  }
}

传入的参数reladdr为.rel.dyn节的起始地址,relsize为.rel.dyn和.rel.plt节的总大小,因此end为.rel.plt的结束地址。参数nrelative为.rel.dyn中类型为R_X86_64_RELATIVE的项的个数。
首先从ld.so对应的link_map结构中获取符号表,relative为.rel.dyn的起始地址,r累加后变为.rel.dyn中最后一个类型为R_X86_64_RELATIVE的项的结束地址,也即第一个类型为R_X86_64_GLOB_DAT的起始地址。
首先通过DO_ELF_MACHINE_REL_RELATIVE宏对.rel.dyn节中类型为R_X86_64_RELATIVE的项进行重定位。然后通过elf_machine_rel函数对.rel.dyn中剩余的项进行重定位。其中重定位项中的r_info成员变量存储了该重定位项在符号表中的索引信息。

elf/do-rel.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC->elf_dynamic_do_Rel->DO_ELF_MACHINE_REL_RELATIVE

# define DO_ELF_MACHINE_REL_RELATIVE(map, l_addr, relative) 
  elf_machine_rel_relative (l_addr, relative,                     
                (void *) (l_addr + relative->r_offset))

auto inline void
__attribute ((always_inline))
elf_machine_rela_relative (Elf64_Addr l_addr, const Elf64_Rela *reloc,
               void *const reloc_addr_arg)
{
  Elf64_Addr *const reloc_addr = reloc_addr_arg;
  *reloc_addr = l_addr + reloc->r_addend;
}

DO_ELF_MACHINE_REL_RELATIVE宏定义继而调用elf_machine_rel_relative函数,elf_machine_rel_relative的宏定义为elf_machine_rela_relative,传入的参数l_addr为ld.so的实际装载地址,reloc为.rel.dyn中类型为R_X86_64_RELATIVE的项的地址,reloc_addr_arg为需要更改值的最终地址(例如在GOT表中),将.rel.dyn中的起始数r_addend加上装载地址ld.so写入最终地址reloc_addr_arg中。

sysdeps/x86_64/dl-machine.h
_dl_start->ELF_DYNAMIC_RELOCATE->_ELF_DYNAMIC_DO_RELOC->elf_dynamic_do_Rel->elf_machine_rela

auto inline void
__attribute__ ((always_inline))
elf_machine_rela (struct link_map *map, const Elf64_Rela *reloc,
          const Elf64_Sym *sym, const struct r_found_version *version,
          void *const reloc_addr_arg, int skip_ifunc)
{
  Elf64_Addr *const reloc_addr = reloc_addr_arg;
  const unsigned long int r_type = ELF64_R_TYPE (reloc->r_info);

  if (__builtin_expect (r_type == R_X86_64_NONE, 0))
    return;
  else
    {
      struct link_map *sym_map = RESOLVE_MAP (&sym, version, r_type);
      Elf64_Addr value = (sym == NULL ? 0
              : (Elf64_Addr) sym_map->l_addr + sym->st_value);

      if (sym != NULL
      && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC,0)
      && __builtin_expect (sym->st_shndx != SHN_UNDEF, 1)
      && __builtin_expect (!skip_ifunc, 1))
    value = ((Elf64_Addr (*) (void)) value) ();

      switch (r_type)
    {
    case R_X86_64_GLOB_DAT:
    case R_X86_64_JUMP_SLOT:
      *reloc_addr = value + reloc->r_addend;
      break;

    ...

    }
    }
}

首先检查.rel.dyn和.rel.plt中项的类型是否为R_X86_64_NONE,如果是则直接返回。
接下来获取符号表中对应符号的地址st_value,加上装载地址l_addr。
ld.so中.rel.dyn节中的项类型有R_X86_64_RELATIVE和R_X86_64_GLOB_DAT两种,.rel.plt中的项类型只有R_X86_64_JUMP_SLOT一种,因此无论哪种类型,最终都加上项中的初始值r_addend填入最终地址reloc_addr中。

elf/rtld.c
_dl_start->_dl_start_final

static inline ElfW(Addr) __attribute__ ((always_inline))
_dl_start_final (void *arg)
{
  ElfW(Addr) start_addr;

  _dl_setup_hash (&GL(dl_rtld_map));
  GL(dl_rtld_map).l_real = &GL(dl_rtld_map);
  GL(dl_rtld_map).l_map_start = (ElfW(Addr)) _begin;
  GL(dl_rtld_map).l_map_end = (ElfW(Addr)) _end;
  GL(dl_rtld_map).l_text_end = (ElfW(Addr)) _etext;
  __libc_stack_end = __builtin_frame_address (0);

  start_addr = _dl_sysdep_start (arg, &dl_main);
  return start_addr;
}

首先通过_dl_setup_hash函数获取ld.so中的hash表并将其设置到全局link_map中(_dl_rtld_map)。
接下来设置l_map_start设置装载的起始地址,即0,l_map_end指向.bss节的结束地址,_etext为.text的结束地址,其中_begin在elf文件夹下的makefile中指定,其余位置在链接脚本中确定。
__builtin_frame_address是gcc提供的用于获得调用栈的地址。
最终调用_dl_sysdep_start函数继续执行。

elf/rtld.c
_dl_start->_dl_start_final->_dl_setup_hash

void
internal_function
_dl_setup_hash (struct link_map *map)
{
  Elf_Symndx *hash;

  if (!map->l_info[DT_HASH])
    return;
  hash = (void *) D_PTR (map, l_info[DT_HASH]);

  map->l_nbuckets = *hash++;
  hash++;
  map->l_buckets = hash;
  hash += map->l_nbuckets;
  map->l_chain = hash;
}

这里通过前面DYNAMIC节的信息获取hash表的地址hash,然后访问该地址,获取桶的数量存储在l_nbuckets中,然后跳过nchain即链表的数量,再访问桶的起始地址存储在l_buckets中,最后将链表地址存储在l_chain中。

elf/dl-sysdep.c
_dl_start->_dl_start_final->_dl_sysdep_start

ElfW(Addr)
_dl_sysdep_start (void **start_argptr,
          void (*dl_main) (const ElfW(Phdr) *phdr, ElfW(Word) phnum,
                   ElfW(Addr) *user_entry, ElfW(auxv_t) *auxv))
{
  const ElfW(Phdr) *phdr = NULL;
  ElfW(Word) phnum = 0;
  ElfW(Addr) user_entry;
  ElfW(auxv_t) *av;

  uid_t uid = 0;
  gid_t gid = 0;
  unsigned int seen = 0;

# define set_seen_secure() (seen = -1)
#  define M(type) (1 << (type))
#  define set_seen(tag) seen |= M ((tag)->a_type)

  __libc_stack_end = DL_STACK_END (start_argptr);
  DL_FIND_ARG_COMPONENTS (start_argptr, _dl_argc, INTUSE(_dl_argv), _environ,
              GLRO(dl_auxv));

  user_entry = (ElfW(Addr)) ENTRY_POINT;
  GLRO(dl_platform) = NULL; 

  for (av = GLRO(dl_auxv); av->a_type != AT_NULL; set_seen (av++))
    switch (av->a_type)
      {
      case AT_PHDR:
    phdr = (void *) av->a_un.a_val;
    break;
      case AT_PHNUM:
    phnum = av->a_un.a_val;
    break;
      case AT_PAGESZ:
    GLRO(dl_pagesize) = av->a_un.a_val;
    break;
      case AT_ENTRY:
    user_entry = av->a_un.a_val;
    break;
      case AT_SECURE:
    seen = -1;
    INTUSE(__libc_enable_secure) = av->a_un.a_val;
    break;
      case AT_PLATFORM:
    GLRO(dl_platform) = (void *) av->a_un.a_val;
    break;
      case AT_HWCAP:
    GLRO(dl_hwcap) = (unsigned long int) av->a_un.a_val;
    break;
      case AT_HWCAP2:
    GLRO(dl_hwcap2) = (unsigned long int) av->a_un.a_val;
    break;
      case AT_CLKTCK:
    GLRO(dl_clktck) = av->a_un.a_val;
    break;
      case AT_FPUCW:
    GLRO(dl_fpu_control) = av->a_un.a_val;
    break;
      case AT_RANDOM:
    _dl_random = (void *) av->a_un.a_val;
    break;
      }

  if (GLRO(dl_pagesize) == 0)
    GLRO(dl_pagesize) = __getpagesize ();

  DL_SYSDEP_INIT;
  DL_PLATFORM_INIT;

  if (GLRO(dl_platform) != NULL)
    GLRO(dl_platformlen) = strlen (GLRO(dl_platform));

  if (__sbrk (0) == _end)
    __sbrk (GLRO(dl_pagesize)
        - ((_end - (char *) 0) & (GLRO(dl_pagesize) - 1)));

  (*dl_main) (phdr, phnum, &user_entry, GLRO(dl_auxv));
  return user_entry;
}

首先通过DL_FIND_ARG_COMPONENTS宏获取栈中的数据,根据sys_execve的源码可知这里依次保存了参数数量、用户变量、环境变量和其他变量(该变量在linux源码的create_elf_table函数中设置),DL_FIND_ARG_COMPONENTS宏将这些变量分别存储在用户空间的_dl_argc、_dl_argv、_environ和_dl_auxv中。
接着初始化user_entry为ENTRY_POINT。然后遍历create_elf_table函数中设置的变量,依次设置到各个变量中,其中最重要的就是获取用户程序的入口设置到user_entry中。再往下设置_dl_pagesize和_dl_platformlen变量。
然后通过DL_SYSDEP_INIT宏用于设置堆的起始地址,再通过DL_PLATFORM_INIT检查相应变量。接着调整堆的起始地址和页面对齐。
最后调用dl_main准备为用户程序的执行搭建环境,传入的参数phdr为用户程序的程序头,phnum为程序头的个数,user_entry为程序的起始点,_dl_auxv为create_elf_table中设置的变量,dl_main函数留在下一章分析。

elf/dl-sysdep.c
_dl_start->_dl_start_final->_dl_sysdep_start->DL_FIND_ARG_COMPONENTS

# define DL_FIND_ARG_COMPONENTS(cookie, argc, argv, envp, auxp) 
  do {                                        
    void **_tmp;                                  
    (argc) = *(long int *) cookie;                        
    (argv) = (char **) ((long int *) cookie + 1);                 
    (envp) = (argv) + (argc) + 1;                         
    for (_tmp = (void **) (envp); *_tmp; ++_tmp)                  
      continue;                                   
    (auxp) = (void *) ++_tmp;                             
  } while (0)

cookie是栈的指针,这里移动该指针依次获取argc、argv、envp和auxp。

sysdeps/unix/sysv/linux/dl-sysdep.c
_dl_start->_dl_start_final->_dl_sysdep_start->DL_SYSDEP_INIT

# define DL_SYSDEP_INIT frob_brk ()

static inline void
frob_brk (void)
{
  __brk (0);
}

int
__brk (void *addr)
{
  void *newbrk;

  __curbrk = newbrk = (void *) INLINE_SYSCALL (brk, 1, addr);
  return 0;
}

SYSCALL_DEFINE1(brk, unsigned long, brk)
{
    ...

    min_brk = mm->start_brk;
    if (brk < min_brk)
        goto out;

    ...

    retval = mm->brk;
    return retval;
}

__brk函数会执行系统调用sys_brk,但是由于传入的参数为0,小于start_brk,因此最后只会返回当前堆的位置,但是由于当前进程还没有使用堆,因此实际上返回的就是堆的起始地址start_brk,然后返回到glibc中,将其保存到__curbrk中。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/conansonic/article/details/54236335

智能推荐

使用JDBC连接数据库出现 The server time zone value ‘�й���׼ʱ��‘ is unrecognized or represents more than one解决方案_jdbc.properties timezone-程序员宅基地

文章浏览阅读553次。在 jdbc.properties 文件中的 url 后面加上 ?serverTimezone=UTC加入之前的jdbc.properties文件:user=rootpassword=12345678url=jdbc:mysql://localhost:3306/testdriverClass=com.mysql.cj.jdbc.Driver加入之后:user=rootpassword=12345678url=jdbc:mysql://localhost:3306/test?serv_jdbc.properties timezone

计算机图形学孔令德基础知识,计算机图形学基础教程孔令德答案-程序员宅基地

文章浏览阅读1.4k次。计算机图形学基础教程孔令德答案【篇一:大学计算机图形学课程设】息科学与工程学院课程设计任务书题目:小组成员:巴春华、焦国栋成员学号:专业班级:计算机科学与技术、2009级本2班课程:计算机图形学指导教师:燕孝飞职称:讲师完成时间: 2011年12 月----2011年 12 月枣庄学院信息科学与工程学院制2011年12 月20日课程设计任务书及成绩评定12【篇二:计算机动画】第一篇《计算机图形学》..._计算机图形学基础教程 孔令德 答案

python xlwings追加数据_大数据分析Python库xlwings提升Excel工作效率教程-程序员宅基地

文章浏览阅读1k次。原标题:大数据分析Python库xlwings提升Excel工作效率教程Excel在当今的企业中非常非常普遍。在AAA教育,我们通常建议出于很多原因使用代码,并且我们的许多数据科学课程旨在教授数据分析和数据科学的有效编码。但是,无论您偏爱使用大数据分析Python的程度如何,最终,有时都需要使用Excel来展示您的发现或共享数据。但这并不意味着仍然无法享受大数据分析Python的某些效率!实际上,..._xlwings通过索引添加数据

java8u211_jre864位u211-程序员宅基地

文章浏览阅读911次。iefans为用户提供的jre8 64位是针对64位windows平台而开发的java运行环境软件,全称为java se runtime environment 8,包括Java虚拟机、Java核心类库和支持文件,不包含开发工具--编译器、调试器和其它工具。jre需要辅助软件--JavaPlug-in--以便在浏览器中运行applet。本次小编带来的是jre8 64位官方版下载,版本小号u211版..._jre8是什么

kasp技术原理_KASP基因分型-程序员宅基地

文章浏览阅读5k次。KASP基因分型介绍KASP(Kompetitive Allele-Specific PCR),即竞争性等位基因特异性PCR,原理上与TaqMan检测法类似,都是基于终端荧光信号的读取判断,每孔反应都是采用双色荧光检测一个SNP位点的两种基因型,不同的SNP对应着不同的荧光信号。KASP技术与TaqMan法类似,它与TaqMan技术不同的是,它不需要每个SNP位点都合成特异的荧光引物,它基于独特的..._kasp是什么

华为p50预装鸿蒙系统,华为p50会不会预装鸿蒙系统_华为p50会预装鸿蒙系统吗-程序员宅基地

文章浏览阅读154次。华为现在比较火的还真就是新开发的鸿蒙系统了,那么在即将上市的华为p50手机上会不会预装鸿蒙系统呢?接下来我们就来一起了解一下华为官方发布的最新消息吧。1.华为p50最新消息相信大家都知道,随着华为鸿蒙OS系统转正日期临近,似乎全网的花粉们都在关注华为鸿蒙OS系统优化、生态建设等等,直接忽略了不断延期发布的华为P50手机,如今华为P50系列手机终于传来了最新的好消息,在经过一系列方案修改以后,终于被..._华为手机p50直接预装鸿蒙系统

随便推点

python用什么软件编程好-初学python编程,有哪些不错的软件值得一用?-程序员宅基地

文章浏览阅读2.1k次。Python编程的软件其实许多,作为一门面向大众的编程言语,许多修正器都有对应的Python插件,当然,也有特地的PythonIDE软件,下面我简单引见几个不错的Python编程软件,既有修正器,也有IDE,感兴味的朋友可以本人下载查验一下:1.VSCode:这是一个轻量级的代码修正器,由微软规划研发,免费、开源、跨途径,轻盈活络,界面精练,支撑常见的自动补全、语法提示、代码高亮、Git等功用,插..._python入门学什么好

pytorch一步一步在VGG16上训练自己的数据集_torch vgg训练自己的数据集-程序员宅基地

文章浏览阅读3.2w次,点赞30次,收藏307次。准备数据集及加载,ImageFolder在很多机器学习或者深度学习的任务中,往往我们要提供自己的图片。也就是说我们的数据集不是预先处理好的,像mnist,cifar10等它已经给你处理好了,更多的是原始的图片。比如我们以猫狗分类为例。在data文件下,有两个分别为train和val的文件夹。然后train下是cat和dog两个文件夹,里面存的是自己的图片数据,val文件夹同train。这样我们的..._torch vgg训练自己的数据集

毕业论文管理系统设计与实现(论文+源码)_kaic_论文系统设计法-程序员宅基地

文章浏览阅读968次。论文+系统+远程调试+重复率低+二次开发+毕业设计_论文系统设计法

在python2与python3中转义字符_Python 炫技操作:五种 Python 转义表示法-程序员宅基地

文章浏览阅读134次。1. 为什么要有转义?ASCII 表中一共有 128 个字符。这里面有我们非常熟悉的字母、数字、标点符号,这些都可以从我们的键盘中输出。除此之外,还有一些非常特殊的字符,这些字符,我通常很难用键盘上的找到,比如制表符、响铃这种。为了能将那些特殊字符都能写入到字符串变量中,就规定了一个用于转义的字符 \ ,有了这个字符,你在字符串中看的字符,print 出来后就不一定你原来看到的了。举个例子>..._pytyhon2、python3对%转义吗

java jar 文件 路径问题_「问答」解决jar包运行时相对路径问题-程序员宅基地

文章浏览阅读1.3k次。我这几天需要做一个Java程序,需要通过jar的形式运行,还要生成文件。最终这个程序是要给被人用的,可能那个用的人还不懂代码。于是我面临一个问题:生成的文件一定不能存绝对路径。刚开始我想得很简单,打绝对路径改成相对路径不就行了吗?于是有了这样的代码:String path = "../test.txt";File file = new File(path);……这个写法本身并没有问题,直接运行代码..._jar启动文件路径中存在!

微信读书vscode插件_曾经我以为 VSCode 是程序员专属的工具,直到发现了这些……...-程序员宅基地

文章浏览阅读598次。如果你知道 VSCode,一说起它,你可能第一个想到的就是把它当做一个代码编辑器,而它的界面应该可能大概率是这样的——如果你恰好又是个程序员,那你可能经常会用到它,不管是 Python、JS 还是 C++ 等各种语言对应的文件,都可以用它来进行简单的编辑和整理,甚至是运行和 debug......但是今天要讲的显然不是这些,经过小美的多方研究,发现了即使是对于大多数并不了解 VSCode,也完全不..._vscode weixin read

推荐文章

热门文章

相关标签