虚拟内存探究 -- 第一篇:C strings & /proc

http://blog.coderhuo.tech/2017/10/12/Virtual_Memory_C_strings_proc/

看不懂哦.
 

这是虚拟内存系列文章的第一篇。
本文通过实验的手段, 带大家了解一些计算机科学相关的基础知识。 
在本文,我们将利用/proc查找进程虚拟内存中的ASCII字符串, 然后修改该字符串。
在这一过程中,我们将学到很多有趣的东西。

一、实验环境

所有的脚本和程序都在下面的环境中测试过:

  • Ubuntu 14.04 LTS

    • Linux ubuntu 4.4.0-31-generic #50~14.04.1-Ubuntu SMP Wed Jul 13 01:07:32 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux
  • gcc
    • gcc (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
  • Python 3
    • Python 3.4.3 (default, Nov 17 2016, 01:08:31)
    • [GCC 4.8.4] on linux

二、预备知识

阅读本文所需知识:

  • C语言基础
  • 些许Python知识
  • 了解Linux的文件系统和shell命令

三、虚拟内存

在计算机领域, 虚拟内存是通过软硬件结合实现的一种内存管理技术, 它将程序所使用的内存地址(虚拟内存地址)映射到计算机的物理内存上(物理内存地址),这使得每个程序看到的内存地址空间都是连续的(或是一些连续地址空间的集合)。操作系统管理虚拟地址空间, 以及虚拟地址空间到物理内存的映射。CPU中的地址转换硬件(通常被称为内存管理单元, MMU)自动将虚拟内存地址转换成物理内存地址。操作系统可以提供比实际物理内存更多的虚拟内存,这一行为是通过操作系统中的软件来实现的。

虚拟内存的主要好处包含以下几点:

  • 将应用程序从内存管理中解放出来, 应用程序只需关心自己的逻辑
  • 不同应用程序间的虚拟内存是相互隔离的, 所以安全性增加了
  • 结合内存分页管理技术, 应用程序理论上可使用比物理内存更多的内存空间

有关虚拟内存的知识, 可进一步阅读维基百科上的相关介绍:虚拟内存

虚拟内存探究 – 第三篇:一步一步画虚拟内存图中,我们将探索虚拟内存的更多细节,并且看下虚拟内存中都有些什么, 以及这些东西分别位于虚拟内存的什么地方。

继续阅读本文前, 你需要知道以下几点:

  • 每个进程都有自己独立的虚拟内存
  • 虚拟内存大小依赖于计算机系统架构
  • 不同的操作系统对虚拟内存的处理会有所不同, 对于现代的大多数操作系统来说, 虚拟内存如下所示:

在虚拟内存的高地址空间,我们可以看到(下面仅列出了部分内容,并非全部):

  • 命令行参数和环境变量
  • “向下”生长的栈。咋看之下这是违反直觉的,但这确实是虚拟内存中栈的实现方式。

在虚拟内存的低地址空间, 我们可以看到:

  • 可执行程序(实际上远比这复杂,但对于理解本文剩余内容足够了)
  • “向上”生长的堆

堆是虚拟内存的一部分,动态分配的内存(比如用malloc分配的内存)位于堆中。

请时刻记住, 虚拟内存和物理内存是不同的

四、剖析一个简单的C程序

我们从一个简单的C程序开始:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

/**
 * main - uses strdup to create a new string, and prints the
 * address of the new duplcated string
 *
 * Return: EXIT_FAILURE if malloc failed. Otherwise EXIT_SUCCESS
 */
int main(void)
{
    char *s;

    s = strdup("Holberton");
    if (s == NULL)
    {
        fprintf(stderr, "Can't allocate mem with malloc\n");
        return (EXIT_FAILURE);
    }
    printf("%p\n", (void *)s);
    return (EXIT_SUCCESS);
}

strdup

继续阅读本文前,请想一想strdup是如何复制字符串Holberton的?又该如何证明呢?

.

.

.

strdup必须创建一个新的字符串,所以它首先会为新字符串手分配内存, 并且可能使用malloc分配内存。这可以通过strdup的man手册确认:

DESCRIPTION
       The  strdup()  function returns a pointer to a new string which is a duplicate of the string s.
       Memory for the new string is obtained with malloc(3), and can be freed with free(3).

等等,基于上文关于虚拟内存的知识,你认为新创建的字符串位于虚拟内存的哪里呢?是高地址空间还是低地址空间?

.

.

.

应该是位于低地址空间(堆中)。为了验证我们的推断,让我们编译并执行这个C程序:


julien@holberton:~/holberton/w/hackthevm0$ gcc -Wall -Wextra -pedantic -Werror main.c -o holberton
julien@holberton:~/holberton/w/hackthevm0$ ./holberton
0x1822010
julien@holberton:~/holberton/w/hackthevm0$ 

新创建的字符串地址是0x1822010。但它究竟是高地址还是低地址呢?

进程的虚拟内存空间多大

进程虚拟地址空间的大小依赖于计算机系统架构。我运行本例使用的是64位机器,所以理论上每个进程的虚拟内存是2^64字节,内存最高地址是0xffffffffffffffff (1.8446744e+19),最低地址是0x0

0x1822010小于0xffffffffffffffff,所以新字符串可能是位于低地址。借助proc文件系统,我们可以确认这一点。

proc文件系统

man proc可见:

The proc filesystem is a pseudo-filesystem which provides an interface to kernel data structures.  It is commonly mounted at `/proc`.  Most of it is read-only, but some files allow kernel variables to be changed.

/proc目录包含一系列文件,我们只关注其中的两个:

  • /proc/[pid]/mem
  • /proc/[pid]/maps

mem文件

man proc可见:

      /proc/[pid]/mem
              This file can be used to access the pages of a process's memory
          through open(2), read(2), and lseek(2).

太棒了!如此看来, 我们可以访问并修改任一进程的虚拟内存。

maps文件

man proc可见:

      /proc/[pid]/maps
              A  file containing the currently mapped memory regions and their access permissions.
          See mmap(2) for some further information about memory mappings.

              The format of the file is:

       address           perms offset  dev   inode       pathname
       00400000-00452000 r-xp 00000000 08:02 173521      /usr/bin/dbus-daemon
       00651000-00652000 r--p 00051000 08:02 173521      /usr/bin/dbus-daemon
       00652000-00655000 rw-p 00052000 08:02 173521      /usr/bin/dbus-daemon
       00e03000-00e24000 rw-p 00000000 00:00 0           [heap]
       00e24000-011f7000 rw-p 00000000 00:00 0           [heap]
       ...
       35b1800000-35b1820000 r-xp 00000000 08:02 135522  /usr/lib64/ld-2.15.so
       35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522  /usr/lib64/ld-2.15.so
       35b1a20000-35b1a21000 rw-p 00020000 08:02 135522  /usr/lib64/ld-2.15.so
       35b1a21000-35b1a22000 rw-p 00000000 00:00 0
       35b1c00000-35b1dac000 r-xp 00000000 08:02 135870  /usr/lib64/libc-2.15.so
       35b1dac000-35b1fac000 ---p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so
       35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so
       35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870  /usr/lib64/libc-2.15.so
       ...
       f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0    [stack:986]
       ...
       7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0   [stack]
       7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0   [vdso]

              The address field is the address space in the process that the mapping occupies.
          The perms field is a set of permissions:

                   r = read
                   w = write
                   x = execute
                   s = shared
                   p = private (copy on write)

              The offset field is the offset into the file/whatever;
          dev is the device (major:minor); inode is the inode on that device.   0  indicates
              that no inode is associated with the memory region,
          as would be the case with BSS (uninitialized data).

              The  pathname field will usually be the file that is backing the mapping.
          For ELF files, you can easily coordinate with the offset field
              by looking at the Offset field in the ELF program headers (readelf -l).

              There are additional helpful pseudo-paths:

                   [stack]
                          The initial process's (also known as the main thread's) stack.

                   [stack:<tid>] (since Linux 3.4)
                          A thread's stack (where the <tid> is a thread ID).
              It corresponds to the /proc/[pid]/task/[tid]/ path.

                   [vdso] The virtual dynamically linked shared object.

                   [heap] The process's heap.

              If the pathname field is blank, this is an anonymous mapping as obtained via the mmap(2) function.
          There is no easy  way  to  coordinate
              this back to a process's source, short of running it through gdb(1), strace(1), or similar.

              Under Linux 2.0 there is no field giving pathname.

这意味着我们可以通过文件/proc/[pid]/mem找到一个正在运行的进程的堆。如果可以读取堆内容,我们就能找到想要修改的字符串。如果能够往堆中写数据,我们就可以进行字符串替换。

pid

进程是可执行程序的一个实例,拥有一个全局唯一的进程ID(PID)。有些函数/系统调用使用PID操作进程或者和进程进行交互。

可以通过命令ps来查看某个进程的pid(具体请参阅man ps)。

五、替换进程的字符串

我们接下来要在一个进程的堆中搜索特定字符串,并用另一个字符串(长度不大于原字符串)替换它。 现在我们已经掌握了所需要的理论知识。

下面这个程序是我们将要hack的程序,正常情况下它循环输出字符串Holberton

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**
 * main - uses strdup to create a new string, loops forever-ever
 *
 * Return: EXIT_FAILURE if malloc failed. Other never returns
 */
int main(void)
{
     char *s;
     unsigned long int i;

     s = strdup("Holberton");
     if (s == NULL)
     {
          fprintf(stderr, "Can't allocate mem with malloc\n");
          return (EXIT_FAILURE);
     }
     i = 0;
     while (s)
     {
          printf("[%lu] %s (%p)\n", i, s, (void *)s);
          sleep(1);
          i++;
     }
     return (EXIT_SUCCESS);
}

编译运行该程序,它将循环输出字符串Holberton直到进程被杀死。

julien@holberton:~/holberton/w/hackthevm0$ gcc -Wall -Wextra -pedantic -Werror loop.c -o loop
julien@holberton:~/holberton/w/hackthevm0$ ./loop
[0] Holberton (0xfbd010)
[1] Holberton (0xfbd010)
[2] Holberton (0xfbd010)
[3] Holberton (0xfbd010)
[4] Holberton (0xfbd010)
[5] Holberton (0xfbd010)
[6] Holberton (0xfbd010)
[7] Holberton (0xfbd010)
...

感兴趣的话,你可以暂停阅读本文,尝试写个脚本/程序寻找进程堆中的字符串。

.

.

.

/proc探究

首先运行刚才编译出来的可执行程序loop.

julien@holberton:~/holberton/w/hackthevm0$ ./loop
[0] Holberton (0x10ff010)
[1] Holberton (0x10ff010)
[2] Holberton (0x10ff010)
[3] Holberton (0x10ff010)
...

然后找到这个进程的ID

julien@holberton:~/holberton/w/hackthevm0$ ps aux | grep ./loop | grep -v grep
julien     4618  0.0  0.0   4332   732 pts/14   S+   17:06   0:00 ./loop

上面的例子中, 进程ID是4618(不同机器上不同时刻运行例程,进程ID都可能不同)。所以,我们将要查看的文件mapsmem位于目录/proc/4618:

  • /proc/4618/maps
  • /proc/4618/mem

在目录/proc/4618下执行ls -la可以看到以下内容:

julien@ubuntu:/proc/4618$ ls -la
total 0
dr-xr-xr-x   9 julien julien 0 Mar 15 17:07 .
dr-xr-xr-x 257 root   root   0 Mar 15 10:20 ..
dr-xr-xr-x   2 julien julien 0 Mar 15 17:11 attr
-rw-r--r--   1 julien julien 0 Mar 15 17:11 autogroup
-r--------   1 julien julien 0 Mar 15 17:11 auxv
-r--r--r--   1 julien julien 0 Mar 15 17:11 cgroup
--w-------   1 julien julien 0 Mar 15 17:11 clear_refs
-r--r--r--   1 julien julien 0 Mar 15 17:07 cmdline
-rw-r--r--   1 julien julien 0 Mar 15 17:11 comm
-rw-r--r--   1 julien julien 0 Mar 15 17:11 coredump_filter
-r--r--r--   1 julien julien 0 Mar 15 17:11 cpuset
lrwxrwxrwx   1 julien julien 0 Mar 15 17:11 cwd -> /home/julien/holberton/w/funwthevm
-r--------   1 julien julien 0 Mar 15 17:11 environ
lrwxrwxrwx   1 julien julien 0 Mar 15 17:11 exe -> /home/julien/holberton/w/funwthevm/loop
dr-x------   2 julien julien 0 Mar 15 17:07 fd
dr-x------   2 julien julien 0 Mar 15 17:11 fdinfo
-rw-r--r--   1 julien julien 0 Mar 15 17:11 gid_map
-r--------   1 julien julien 0 Mar 15 17:11 io
-r--r--r--   1 julien julien 0 Mar 15 17:11 limits
-rw-r--r--   1 julien julien 0 Mar 15 17:11 loginuid
dr-x------   2 julien julien 0 Mar 15 17:11 map_files
-r--r--r--   1 julien julien 0 Mar 15 17:11 maps
-rw-------   1 julien julien 0 Mar 15 17:11 mem
-r--r--r--   1 julien julien 0 Mar 15 17:11 mountinfo
-r--r--r--   1 julien julien 0 Mar 15 17:11 mounts
-r--------   1 julien julien 0 Mar 15 17:11 mountstats
dr-xr-xr-x   5 julien julien 0 Mar 15 17:11 net
dr-x--x--x   2 julien julien 0 Mar 15 17:11 ns
-r--r--r--   1 julien julien 0 Mar 15 17:11 numa_maps
-rw-r--r--   1 julien julien 0 Mar 15 17:11 oom_adj
-r--r--r--   1 julien julien 0 Mar 15 17:11 oom_score
-rw-r--r--   1 julien julien 0 Mar 15 17:11 oom_score_adj
-r--------   1 julien julien 0 Mar 15 17:11 pagemap
-r--------   1 julien julien 0 Mar 15 17:11 personality
-rw-r--r--   1 julien julien 0 Mar 15 17:11 projid_map
lrwxrwxrwx   1 julien julien 0 Mar 15 17:11 root -> /
-rw-r--r--   1 julien julien 0 Mar 15 17:11 sched
-r--r--r--   1 julien julien 0 Mar 15 17:11 schedstat
-r--r--r--   1 julien julien 0 Mar 15 17:11 sessionid
-rw-r--r--   1 julien julien 0 Mar 15 17:11 setgroups
-r--r--r--   1 julien julien 0 Mar 15 17:11 smaps
-r--------   1 julien julien 0 Mar 15 17:11 stack
-r--r--r--   1 julien julien 0 Mar 15 17:07 stat
-r--r--r--   1 julien julien 0 Mar 15 17:11 statm
-r--r--r--   1 julien julien 0 Mar 15 17:07 status
-r--------   1 julien julien 0 Mar 15 17:11 syscall
dr-xr-xr-x   3 julien julien 0 Mar 15 17:11 task
-r--r--r--   1 julien julien 0 Mar 15 17:11 timers
-rw-r--r--   1 julien julien 0 Mar 15 17:11 uid_map
-r--r--r--   1 julien julien 0 Mar 15 17:11 wchan

/proc/pid/maps

如之前所见,文件/proc/pid/maps是个文本文件,我们可以直接读取,内容如下:

julien@ubuntu:/proc/4618$ cat maps
00400000-00401000 r-xp 00000000 08:01 1070052                            /home/julien/holberton/w/funwthevm/loop
00600000-00601000 r--p 00000000 08:01 1070052                            /home/julien/holberton/w/funwthevm/loop
00601000-00602000 rw-p 00001000 08:01 1070052                            /home/julien/holberton/w/funwthevm/loop
010ff000-01120000 rw-p 00000000 00:00 0                                  [heap]
7f144c052000-7f144c20c000 r-xp 00000000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c20c000-7f144c40c000 ---p 001ba000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c40c000-7f144c410000 r--p 001ba000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c410000-7f144c412000 rw-p 001be000 08:01 136253                     /lib/x86_64-linux-gnu/libc-2.19.so
7f144c412000-7f144c417000 rw-p 00000000 00:00 0
7f144c417000-7f144c43a000 r-xp 00000000 08:01 136229                     /lib/x86_64-linux-gnu/ld-2.19.so
7f144c61e000-7f144c621000 rw-p 00000000 00:00 0
7f144c636000-7f144c639000 rw-p 00000000 00:00 0
7f144c639000-7f144c63a000 r--p 00022000 08:01 136229                     /lib/x86_64-linux-gnu/ld-2.19.so
7f144c63a000-7f144c63b000 rw-p 00023000 08:01 136229                     /lib/x86_64-linux-gnu/ld-2.19.so
7f144c63b000-7f144c63c000 rw-p 00000000 00:00 0
7ffc94272000-7ffc94293000 rw-p 00000000 00:00 0                          [stack]
7ffc9435e000-7ffc94360000 r--p 00000000 00:00 0                          [vvar]
7ffc94360000-7ffc94362000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

回想前面的内容,可以看到,栈([stack])位于内存的高地址,堆([heap])位于内存的低地址。

[heap]

maps文件中, 我们可以找到搜索字符串需要的所有信息:

010ff000-01120000 rw-p 00000000 00:00 0                                  [heap]

这个进程的堆信息如下:

  • 在虚拟内存中的起始地址是0x010ff000
  • 结束地址是01120000
  • 权限是可读写的(rw

回顾下正在运行的loop的输出:

...
[1024] Holberton (0x10ff010)
...

0x010ff000 < 0x10ff010 < 0x01120000。这证明了我们的字符串是在堆上。更精确的说,新字符串是在堆偏移0x10的地方。如果我们打开文件/proc/4618/mem并且将文件指针移动到0x10ff010, 我们就能替换正在运行的程序loop中的字符串Holberton

我们接下来会写个程序/脚本做这件事情。
你也可以暂停阅读本文,用自己最熟悉的语言尝试写个脚本/程序来做这件事情。

.

.

.

替换虚拟内存中的字符串###

下面是我们用Python3 实现字符串替换的脚本(read_write_heap.py):

#!/usr/bin/env python3
'''
Locates and replaces the first occurrence of a string in the heap
of a process    

Usage: ./read_write_heap.py PID search_string replace_by_string
Where:
- PID is the pid of the target process
- search_string is the ASCII string you are looking to overwrite
- replace_by_string is the ASCII string you want to replace
  search_string with
'''

import sys

def print_usage_and_exit():
    print('Usage: {} pid search write'.format(sys.argv[0]))
    sys.exit(1)

# check usage
if len(sys.argv) != 4:
    print_usage_and_exit()

# get the pid from args
pid = int(sys.argv[1])
if pid <= 0:
    print_usage_and_exit()
search_string = str(sys.argv[2])
if search_string  == "":
    print_usage_and_exit()
write_string = str(sys.argv[3])
if search_string  == "":
    print_usage_and_exit()

# open the maps and mem files of the process
maps_filename = "/proc/{}/maps".format(pid)
print("[*] maps: {}".format(maps_filename))
mem_filename = "/proc/{}/mem".format(pid)
print("[*] mem: {}".format(mem_filename))

# try opening the maps file
try:
    maps_file = open('/proc/{}/maps'.format(pid), 'r')
except IOError as e:
    print("[ERROR] Can not open file {}:".format(maps_filename))
    print("        I/O error({}): {}".format(e.errno, e.strerror))
    sys.exit(1)

for line in maps_file:
    sline = line.split(' ')
    # check if we found the heap
    if sline[-1][:-1] != "[heap]":
        continue
    print("[*] Found [heap]:")

    # parse line
    addr = sline[0]
    perm = sline[1]
    offset = sline[2]
    device = sline[3]
    inode = sline[4]
    pathname = sline[-1][:-1]
    print("\tpathname = {}".format(pathname))
    print("\taddresses = {}".format(addr))
    print("\tpermisions = {}".format(perm))
    print("\toffset = {}".format(offset))
    print("\tinode = {}".format(inode))

    # check if there is read and write permission
    if perm[0] != 'r' or perm[1] != 'w':
        print("[*] {} does not have read/write permission".format(pathname))
        maps_file.close()
        exit(0)

    # get start and end of the heap in the virtual memory
    addr = addr.split("-")
    if len(addr) != 2: # never trust anyone, not even your OS :)
        print("[*] Wrong addr format")
        maps_file.close()
        exit(1)
    addr_start = int(addr[0], 16)
    addr_end = int(addr[1], 16)
    print("\tAddr start [{:x}] | end [{:x}]".format(addr_start, addr_end))

    # open and read mem
    try:
        mem_file = open(mem_filename, 'rb+')
    except IOError as e:
        print("[ERROR] Can not open file {}:".format(mem_filename))
        print("        I/O error({}): {}".format(e.errno, e.strerror))
        maps_file.close()
        exit(1)

    # read heap
    mem_file.seek(addr_start)
    heap = mem_file.read(addr_end - addr_start)

    # find string
    try:
        i = heap.index(bytes(search_string, "ASCII"))
    except Exception:
        print("Can't find '{}'".format(search_string))
        maps_file.close()
        mem_file.close()
        exit(0)
    print("[*] Found '{}' at {:x}".format(search_string, i))

    # write the new string
    print("[*] Writing '{}' at {:x}".format(write_string, addr_start + i))
    mem_file.seek(addr_start + i)
    mem_file.write(bytes(write_string, "ASCII"))

    # close files
    maps_file.close()
    mem_file.close()

    # there is only one heap in our example
    break

注意:需要以root权限执行上面的脚本, 否则无法读写文件/proc/pid/mem, 即使你是进程的所有者。

运行上面的脚本:

julien@holberton:~/holberton/w/hackthevm0$ sudo ./read_write_heap.py 4618 Holberton "Fun w vm!"
[*] maps: /proc/4618/maps
[*] mem: /proc/4618/mem
[*] Found [heap]:
    pathname = [heap]
    addresses = 010ff000-01120000
    permisions = rw-p
    offset = 00000000
    inode = 0
    Addr start [10ff000] | end [1120000]
[*] Found 'Holberton' at 10
[*] Writing 'Fun w vm!' at 10ff010
julien@holberton:~/holberton/w/hackthevm0$

可以看到上面脚本打印出来的地址和我们手动找到的是一致的:

  • 进程的堆位于虚拟内存的0x010ff000 ~ 0x01120000
  • 我们要找的字符串地址是0x10ff010, 相对于堆的起始地址偏移了0x10

回过头来看下我们的loop程序,它应该会打印字符串”fun w vm!”

...
[2676] Holberton (0x10ff010)
[2677] Holberton (0x10ff010)
[2678] Holberton (0x10ff010)
[2679] Holberton (0x10ff010)
[2680] Holberton (0x10ff010)
[2681] Holberton (0x10ff010)
[2682] Fun w vm! (0x10ff010)
[2683] Fun w vm! (0x10ff010)
[2684] Fun w vm! (0x10ff010)
[2685] Fun w vm! (0x10ff010)
...

六、下节预告

下一篇文章中我们要做的事情和本章类似, 不同的是我们将访问并修改一个Python3 脚本的内存。 这做起来比较吃力, 所以我们需要了解Pyhton3 内部的一些机制。不信你可以试试,上面的脚本read_write_heap.py并不能修改Python3进程中的ASCII字符串。

七、继续阅读

八、原文链接

Hack The Virtual Memory: C strings & /proc


Similar Posts

[转帖]虚拟内存探究 -- 第一篇:C strings & /proc的更多相关文章

  1. [转帖]虚拟内存探究 -- 第二篇:Python 字节

    虚拟内存探究 -- 第二篇:Python 字节 http://blog.coderhuo.tech/2017/10/15/Virtual_Memory_python_bytes/ 是真看不懂哦     ...

  2. [转帖]虚拟内存探究 -- 第四篇:malloc, heap & the program break

    虚拟内存探究 -- 第四篇:malloc, heap & the program break http://blog.coderhuo.tech/2017/10/19/Virtual_Memo ...

  3. 【转帖】Mysql多维数据仓库指南 第一篇 第1章

     Mysql多维数据仓库指南 第一篇基本原理 章节列表: 第1章:基本组成 第2章:维度历史 第3章:维度可加性 第4章:维度查询 本篇概述 你将运用关系数据库来实施一个维度数据仓库.事实表和维表这两 ...

  4. Android基础学习第一篇—Project目录结构

    写在前面的话: 1. 最近在自学Android,也是边看书边写一些Demo,由于知识点越来越多,脑子越来越记不清楚,所以打算写成读书笔记,供以后查看,也算是把自己学到所理解的东西写出来,献丑,如有不对 ...

  5. Windows系统CPU内存网络性能统计第一篇 内存

    最近翻出以前做过的Windows系统性能统计程序,这个程序可以统计系统中的CPU使用情况,内存使用情况以及网络流量.现在将其整理一下(共有三篇),希望对大家有所帮助. 目录如下: 1.<Wind ...

  6. vue+uni-app商城实战 | 第一篇:【有来小店】微信小程序快速开发接入Spring Cloud OAuth2认证中心完成授权登录

    一. 前言 本篇通过实战来讲述如何使用uni-app快速进行商城微信小程序的开发以及小程序如何接入后台Spring Cloud微服务. 有来商城 youlai-mall 项目是一套全栈商城系统,技术栈 ...

  7. 前端学习 linux —— 第一篇

    前端学习 linux - 第一篇 本文主要介绍"linux 发行版本"."cpu 架构"."Linux 目录结构"."vi 和 v ...

  8. 从0开始搭建SQL Server AlwaysOn 第一篇(配置域控)

    从0开始搭建SQL Server AlwaysOn 第一篇(配置域控) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://www.cnb ...

  9. Python爬虫小白入门(四)PhatomJS+Selenium第一篇

    一.前言 在上一篇博文中,我们的爬虫面临着一个问题,在爬取Unsplash网站的时候,由于网站是下拉刷新,并没有分页.所以不能够通过页码获取页面的url来分别发送网络请求.我也尝试了其他方式,比如下拉 ...

随机推荐

  1. Python之网路编程进程理论基础

    背景知识 顾名思义,进程即一个软件正在进行的过程.进程是对正在运行程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老的也是最重要的抽象概念之一.操作系统的其他 ...

  2. 在HTML页面加载完毕后运行某个js

    js <script type="text/javascript"> window.onload=function(){ //执行} </script> j ...

  3. JVM体系结构及优化

    源文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/index.html JVM体系结构     方法区,类加载器,堆,Java ...

  4. ApplicationRunner接口

    ApplicationRunner 和 CommandLineRunner 功能一致,用法也基本一致,唯一的区别主要体现在对参数的处理上,ApplicationRunner 可以接收更多类型的参数(A ...

  5. Dw 表单制作 与 dedecms 结合实现提交效果

    Dw 表单制作 与 dedecms 结合实现提交效果 自定义表单的用处 1.教育类网站的学员报名 2.企业网站的在线订单 3.普通网页上的一些和用户交互的小功能 实现原理:首先添加表单:核心-频道模型 ...

  6. linux-系统启动流程-7

    1,BIOS开机自检,检查cpu硬件及开机启动顺序,查找第一个磁盘磁头的MBR信息并加载BOOtloader,然后将控制权交与bootloader 2, GRUB GRUB(Grand Unified ...

  7. Selenium-webdriver+八种元素定位

    进行Web页面自动化测试,对页面上的元素进行定位和操作是核心.而操作又是以定位为前提的,因此,对页面元素的定位是进行自动化测试的基础. 页面上的元素就像人一样,有各种属性,比如元素名字,元素id,元素 ...

  8. [CSP-S模拟测试]:山屋惊魂(模拟)

    题目传送门(内部题90) 输入格式 前四行依次表示每种属性:$Might$.$Speed$.$Sanity$.$Knowledge$.每行一个$8$位数表示该属性的$8$个档的值,第二个数表示初始在哪 ...

  9. pycharm 调用turtle模块时,窗口闪屏不能显示

    #如下代码时,在pycharm中运行时,窗口在程序结束后,直接关闭,不能看到绘制的图像. #在python自带的IDE中,在执行代码后,可以看到窗口的显示. import turtle t = tur ...

  10. 【重点突破】—— UniApp微信小程序开发教程学习Three

    一.实战 HBuilderX:在微信小程序中运行页面,需要设置->安全 开启微信小程序服务端口,HBuilder工具->设置->配置程序路径 网络请求.模板语法.打开页面.页面传参 ...