哈希表-快乐数
这道题也是用C++的unordered_set
比较简单,题目中提到无限循环,也就是sum是出现过的,也就陷入了无限循环了已经,这个时候就返回false。
1 | class Solution { |
这道题也是用C++的unordered_set
比较简单,题目中提到无限循环,也就是sum是出现过的,也就陷入了无限循环了已经,这个时候就返回false。
1 | class Solution { |
这道题用C++的unordered_set
比较简单,这个容器是个无序的set容器,容器内部存的值都不相等,并且不能被修改,并非以键值对的形式存储数据,而是直接存储数据的值。(键和值相等)
1 | bool isAnagram(char * s, char * t){ |
遍历字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可, 这样就将字符串s中字符出现的次数,统计出来了。
那看一下如何检查字符串t中是否出现了这些字符,同样在遍历字符串t的时候,对t中出现的字符映射哈希表索引上的数值再做-1的操作。
那么最后检查一下,record数组如果有的元素不为零0,说明字符串s和t一定是谁多了字符或者谁少了字符,return false。
1 | bool isAnagram(char * s, char * t){ |
插入排序的基本思想是,维护一个有序序列,初始时有序序列只有一个元素,每次将一个新的元素插入到有序序列中,将有序序列的长度增加 1,直到全部元素都加入到有序序列中。
1 | struct ListNode* insertionSortList(struct ListNode* head){ |
其实PE文件和ELF文件结构比较类似,学过PE文件后,ELF文件就很好懂。
通俗点说由汇编器和链接器生成的文件都属于ELF文件。通常我们接触的ELF文件主要有以下三类:
relocatable file
) ,一般为.o
文件。可以被链接成可执行文件或共享目标文件。静态链接库属于可重定位文件。excutable file
)它保存了适合直接加载到内存中执行的二进制程序。shared object file
)一般为.so
文件。一种特殊的可重定位目标文件,可以在加载或者运行时被动态的加载进内存并链接。Note:段(Segment
)与节(Section
)的区别。很多地方对两者有所混淆。段是程序执行的必要组成,当多个目标文件链接成一个可执行文件时,会将相同权限的节合并到一个段中。相比而言,节的粒度更小。
ELF文件主要由四部分组成:
ELF Header其实对应的是一个结构体,该结构体定义如下:
1 | #define EI_NIDENT 16 |
1 | ElfN_Addr Unsigned program address, uintN_t |
e_ident
:包含一个magic number、ABI信息,该文件使用的平台、大小端规则e_type
:文件类型, 表示该文件属于可执行文件、可重定位文件、core dump文件或者共享库e_entry
: 表示程序执行的入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位指令一般没有入口地址,则该值为0e_phoff
: 表示Program Header的入口偏移量(以字节为单位)e_shoff
: 表示Section Header的入口偏移量(以字节为单位)e_flags
: 保存了这个ELF文件相关的特定处理器的flage_ehsize
: 表示ELF Header大小(以字节为单位)e_phentsize
: 表示Program Header大小(以字节为单位)e_phnum
: 表示Program Header的数量 (十进制数字)e_shentsize
: 表示单个Section Header大小(以字节为单位)e_shnum
: 表示Section Header的数量 (十进制数字)e_shstrndx
: 表示字符串表的索引,字符串表用来保存ELF文件中的字符串,比如段名、变量名。 然后通过字符串在表中的偏移访问字符串。magic:通过判断该字段可以确定文件格式和类型。如:ELF的可执行文件格式的头4个字节为0x7F
、e
、l
、f
;Java的可执行文件格式的头4个字节为c
、a
、f
、e
;如果被执行的是Shell脚本或perl、python等解释型语言的脚本,那么它的第一行往往是#!/bin/sh
或#!/usr/bin/perl
或#!/usr/bin/python
,此时前两个字节#
和!
就构成了魔数,系统一旦判断到这两个字节,就对后面的字符串进行解析,以确定具体的解释程序路径。
ELF 节头表是一个节头数组。每一个节头都描述了其所对应的节的信息,如节名、节大小、在文件中的偏移、读写权限等。编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。
1 | typedef struct { |
sh_name
:表示该section的名字,节名是一个字符串,保存在一个名为.shstrtab
的字符串表(可通过Section Header索引到)。sh_name的值实际上是其节名字符串在.shstrtab
中的偏移值。
sh_type
:表示该section中存放的内容类型,比如符号表,可重定位段等。
sh_flags
: 表示该section的一些属性,比如是否可写,可执行等。
sh_addr
: 表示该section在程序运行时的内存地址
sh_offset
: 如果该节存在于文件中,则表示该节在文件中的偏移;否则无意义,如sh_offset对于BSS 节来说是没有意义的
sh_size
: 表示该section的大小
sh_link
: 配合sh_info保存section的额外信息
sh_info
:保存该section相关的一些额外信息
sh_addralign
:表示该section需要的地址对齐信息
sh_entsize
:有些section里保存的是一些固定长度的条目,比如符号表。对于这些section来讲,sh_entsize里保存的就是条目的长度。
如果节的类型是与链接相关的(无论是动态链接还是静态链接),如重定位表、符号表、等,则sh_link
、sh_info
两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。
在ELF文件中,数据和代码分开存放的,这样可以按照其功能属性分成一些区域,比如程序、数据、符号表等。这些分离存放的区域在ELF文件中反映成section
。ELF文件中典型的section
如下:
.text
: 已编译程序的二进制代码.rodata
: 只读数据段,比如常量.data
: 已初始化的全局变量和静态变量.bss
: 未初始化的全局变量和静态变量,所有被初始化成0的全局变量和静态变量(记得与PE文件区分开).sysmtab
: 符号表,它存放了程序中定义和引用的函数和全局变量的信息.debug
: 调试符号表,它需要以'-g'
选项编译才能得到,里面保存了程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C文件.line
: 原始的C文件行号和.text节
中机器指令之间的映射.strtab
: 字符串表,内容包括.symtab
和.debug
节中的符号表特殊的,
1)对于可重定位的文件,由于在编译时,并不能确定它引用的外部函数和变量的地址信息,因此,编译器在生成目标文件时,增加了两个section:
.rel.text
保存了程序中引用的外部函数的重定位信息,这些信息用于在链接时重定位其对应的符号。.rel.data
保存了被模块引用或定义的所有全局变量的重定位信息,这些信息用于在链接时重定位其对应的全局变量。2)对于可执行文件,由于它已经全部完成了重定位工作,可以直接加载到内存中执行,所以它不存在.rel.text
和.rel.data
这两个section。但是,它增加了一个section:
.init
: 这个section里面保存了程序运行前的初始化代码重定位表是一个Elf_Rel
类型的数组结构,每一项对应一个需要进行重定位的项。
其成员含义如下表所示:
在可执行文件中,ELF header下面紧接着就是Program Header Table。它描述了各个segment在ELF文件中的位置以及在程序执行过程中系统需要准备的其他信息。它也是用一个结构体数组来表示的。具体代码如下:
1 | typedef uint64_t Elf64_Addr; |
p_type
:描述了当前segment是何种类型的或者如何解释当前segment,比如是动态链接相关的或者可加载类型的等p_flags
:保存了该segment的flagp_offset
:表示从ELF文件到该segment第一个字节的偏移量p_vaddr
:表示该segment的第一个字节在内存中的虚拟地址p_paddr
:对于使用物理地址的系统来讲,这个成员表示该segment的物理地址p_filesz
:表示该segment的大小,以字节表示p_memsz
:表示该segment在内存中的大小,以字节表示p_align
:表示该segment在文件中或者内存中需要以多少字节对齐这道题感觉还是有点难,看了这篇题解觉得很清楚详细。其实代码很简单,理清楚逻辑很重要。
1 | struct ListNode *detectCycle(struct ListNode *head) { |
参考文章:看雪虚拟机保护技术浅谈,人肉跟踪VMProtect入口
所谓虚拟机保护技术,是指将代码翻译为机器和人都无法识别的一串伪代码字节流(字节码);在具体执行时再对这些伪代码进行一一翻译解释,逐步还原为原始代码并执行。字节码它是由指令执行系统定义的一套指令和数据组成的一串数据流。
负责用于翻译伪代码并负责具体执行的子程序就叫做虚拟机VM,好似一个抽象的CPU。它以一个函数的形式存在,函数的参数就是字节码的内存地址。
这张图是一个虚拟机执行时的整图概述,VStartVM部分初始化虚拟机,VMDispatcher负责调度这些Handler,Handler可以理解为一个个的子函数(功能代码),它是每一个伪指令对应的执行功能代码.
为什么要出现一条伪指令对应着一个Handler执行模块呢?
这和虚拟机加壳的指令膨胀有关,被虚拟机加壳后,同样一条指令被翻译成了虚拟伪指令,
一条虚拟伪指令往往对应着好几倍的等效代码,当中可能还加入了花指令,整个Handler加起来可能就等效为原本的一条x86汇编指令。
Bytecode就是虚拟伪指令,在程序中,VMDispatcher往往是一个类while结构,不断的循环读取伪指令,然后执行。
虚拟机不可能针对每一种具体情况都进行翻译处理。必须对所有可能遇到的指令先进行抽象归类,然后分解为若干简单的小指令,再交由各个专门的子程序(handler)去处理。
三元式代码(3地址代码)
即不论多么复杂的赋值公式,都可以分解为数个3地址代码式序列。1段3地址代码只完成1次运算,譬如1次二目运算、1次比较,或者1次分支跳转运算。论多么复杂的指令,都可以分解为一串不可再分割的原子指令序列。
虚拟机(CPU)的体系架构可分为3种,基于堆栈的(Stack based),基于寄存器的(Register based)和3地址机器。基于堆栈的虚拟机体系架构需要频繁操作堆栈,其使用的虚拟寄存器保存在堆栈中,每个原子指令的handler都需要push,pop。譬如指令add,基于堆栈的CPU首先从堆栈里Pop两个数,然后将两数相加,再把和Push到堆栈。Add指令只占用1个字节。而基于寄存器的CPU对应指令为 add Reg1,Reg2,需要3个字节。
虚拟机保护技术,就是把基于寄存器的CPU代码,改造成基于堆栈的CPU的伪代码。然后再由基于堆栈的虚拟机(CPU)对伪代码解释执行。
VStartVM是虚拟机的入口,负责保存运行环境(各个寄存器的值)、以及初始化堆栈(虚拟机使用的变量全部在堆栈中)。Bytecode是伪代码;VMDispatcher对伪代码逐个阅读处理,然后分发给下面的各个子程序(Handler)。 加壳程序先把已知的X86指令解释成了字节码,放在PE文件中,然后将原处代码删掉,改成类似的代码进入虚拟机执行循环。
1 | push bytecode ;伪代码地址 ,作为参数 |
初始化后堆栈如图 :edi指向VMcontext;esi指向伪代码的地址;ebp指向真实堆栈的栈顶; 这三个寄存器在VM内不要再改了。
VMContext是虚拟机VM使用的虚拟环境结构:
1 | Struct VMContext |
用简单的add指令作为示例
1 | Vadd: ;virtual add |
上图是参考文章中的图,和我自己画的那个差不多,区分了颜色来说一下蓝-橙-黄部分作用(一条vmp指令的执行)。
* 蓝色:加载并解密字节码。
前十几个蓝橙黄组合都是一种handler,执行的是一种vPop操作,如上图箭头,是将真实寄存器的值移动到VMcontext
区域,一个vPop移动一个值。
后十几个蓝橙黄组合
最后一个vRet,将压入vmp运算栈中vmp虚拟寄存器值,更新到真实cpu寄存器中,至此,退出虚拟机。
还是简单的双指针问题,我感觉leetcode这道题还是有点问题,skipA/B都没在参数里面
(这道题目看的是节点的地址是否相等)
1 | struct ListNode* getIntersectionNode(struct ListNode* headA, struct ListNode* headB) { |
fast先移动n+1个节点,与slow之间要相隔n个节点,然后fast需要和slow一起移动,直到fast指向末尾(C++/C记得释放被删除的节点
1 | struct ListNode* removeNthFromEnd(struct ListNode* head, int n){ |
1 | class Solution: |
今天试了一下python编程,还是得慢慢把python熟练起来
1 | struct ListNode* swapPairs(struct ListNode* head){ |
1 | class Solution: |