Vimscripts 编程

变量

普通变量

创建变量、变量赋值都需要用到let关键字

1
2
3
4
let foo = "bar"
echo foo
let foo = "foo"
echo foo

数字

  • Number32位带符号整形(整形之间除法同c)

    • :echo 0xef:16进制
    • :echo 017:8进制(鉴于以下,不建议使用)
    • :echo 019:10进制(9不可能出现,vim自动处理)
  • Float

    • :echo 5.1e-3:科学计数法)
    • :echo 5.0e3:科学计数法中一定要有小数点

类型转换:Number和Float运算时会强制转换为Float

字符串

类型转换
  • +if这些“运算”中,vim会强制转换变量类型

    • 数字开头的字符串会转为相应Number(即使符合Float也 会舍弃小数点后)
    • 而非数字开头 则转换为0
  • 连接.

    • .连接时vim可以自动将Number转换为字符串然后连接
    • 但是对于 Float,vim不能自动转换
  • 转义\

    • 注意echom "foo\nbar"类似的输出时,echom不会像 echo一样输出两行,而是将换行输出为vim默认 (即使设置了listchars)的“换行符”
  • 字符串字面量''

    • 所见即所得(py中r’’),注意连续两个单引号表示单引号
  • 内建字符串函数

    • strlen(str)(len(str)效果对字符串同)
    • split(str, token=" ")
    • join([str], token=" ")
    • tolower(str)
    • toupper(str)
  • 字符串比较====?==#

    • ==:对字符串比较是否大小写敏感取决于设置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      set noignorecase
      if "foo"=="Foo"
      echo "bar"(不会输出)
      endif

      set ignorecase
      if "foo"=="Foo"
      echo "bar"(会输出)
      endif
    • ==?:对字符串比较大小写永远不敏感

    • ==#:对字符串比较大小写永远敏感

  • <>:同上,也有3种

集合类型变量

列表

vim列表特点

  • 有序、异质
  • 索引从0开始,可以使用负数索引,使用下标得到对应元素
  • 支持切割
    • 是闭区间(这个和python不同)
    • 可以负数区间切割
    • 可以忽略起始/结尾索引表示从0开始/末尾截至
    • 切割区间越界是安全的

      字符串可以像列表一样切割、索引,但是不可以使用负数 索引,却可以使用负数切割

  • +用于连接两个列表

列表内建函数

  • add(list, item):添加新元素
  • len(list):列表长度
  • get(list, index, default_val):获取列表元素,越界则 返回default_val
  • index(list, item):返回元素索引,不存在返回-1
  • join(list, token):将列表中元素转换为字符串后,使用 toke连接,缺省为<space>
  • reverse(list):反转列表

字典

字典特性

  • 值是异质的,键可以不是字符串,但是会被强制转换为字符串, 因此,在查找值时也可以使用非字符串dict[100],同样会被 强制转换为字符串dict["100"]之后查找

  • 支持属性.查找,甚至可以后接Number

  • 添加新元素就和普通赋值一样:let dict.100 = 100

  • 移除字典中的元素

    • remove(dict, index)
    • unlet dict.index/unlet dict[index] 移除不存在的元素事报错

允许定义时多一个,

内建函数
  • get(dict, index, default_val):同列表

  • has_key(dict, index):检查字典中是否有给定键,返回 1(真)或0(假)

  • item(dict):返回字典键值对,和字典一样无序

  • keys(dict):返回字典所有键

  • values(dict):返回字典所有值

作为变量的选项

  • bool选项输出0、1

    1
    2
    :set wrap
    :set nowrap
  • 键值选项

    1
    2
    :set textwidth=80
    :echo &textwidth
  • 本地选项(l:作用域下)

    1
    let &l:number=1
  • 选项变量还可以参与运算

    1
    2
    let &textwidth=100
    let &textwidht = &textwidth + 10

作为变量的寄存器

1
2
3
4
let @a = "hello"
echo @a
echo @"
echo @/

变量作用域

<char>:开头表示作用域变量

  • 变量默认为全局变量
  • b::当前缓冲区作用域变量
  • g::全局变量

语句

条件语句

vim中没有not关键字,可以使用!表示否定

  • !:否
  • ||:或
  • &&:与
1
2
3
4
5
6
7
8
if "1one"
echo "one"(会输出)
endif
if ! "one"
echo "one"(会输出)
else
echo "two"
endif

finish关键字

finally时结束整个vimscripts的运行

循环语句

for语句

1
2
3
4
5
let c = 0
for i in [1,2,3]
let c+=i
endfor
echom c

while语句

1
2
3
4
5
6
7
let c = 1
let total = 0
while c<=4
let total+=c
let c+=1
endwhile
echom total

函数

没有作用域限制的vimscripts函数必须以大写字母开头 (有作用域限制最好也是大写字母开头)

1
2
3
4
5
6
7
8
func Func(arg1,...)
echo "Func"
echo a:arg1(arg1)
echo a:0(额外(可变)参数数量)
echo a:1(第一个额外参数)
echo a:000(所有额外参数的list
return "Func"
endfunction

function后没有紧跟!时,函数已经被定义,将会给出 错误,而function!时会直接将原函数替换,除非原函数正在 执行,此时仍然报错

  • 调用方式

    • :call Func():call直接调用(return值会被直接丢弃)
    • :echo Func():表达式中调用
  • 函数结束时没有return,隐式返回0

  • 函数参数:最多20个

    • 参数全部位于a:参数作用域下

      • a:arg1:一般参数
      • a:0:额外(可变)参数数量
      • a:n:第n个额外参数
      • a:000:包含额外参数list
    • 参数不能重新赋值

vim中函数可以赋值给变量,同样的此时变量需要大写字母开头, 当然可以作为集合变量的元素,甚至也可以作为参数传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function! Reversed(l)
let nl = deepcopy(a:l)
call reverse(nl)
return nl
endfunction

let Myfunc = function("Reversed")

function! Mapped(func, l)
let nl = deepcopy(a:l)
call map(nl, string(a:func) . '(v:val)')
return nl
endfunction

call Mapped(function("Reversed"), [[3,2], [1,2]])

let funcs = [function("Reversed"), function("Mapped")]

更多函数参见functions.vim(如果完成了)

Execute、Normal

  • :execute:把字符串当作vimscript命令执行(命令行输入) :execute "echom 'hello, world'"<cr>

    大多数语言中应该避免使用”eval”之类构造可执行字符串,但是 vimscripts代码大部分只接受用户输入,安全不是严重问题, 使用:execute命令能够极大程度上简化命令

    :execute命令用于配置文件时,不要忽略结尾的<cr>, 表示“执行命令”

  • :normal:接受一串键值,并当作是normal模式接受按键

    • :normal后的按键会执行映射,:normal!忽略所有映射

    • :normal无法识别“”这样的特殊字符序列 :normal /foo<cr> 这样的命令并不会搜索,因为“没有按回车”

  • :execute和:normal结合使用,让:normal接受按下 “无法打印”字符(<cr><esc>等),execute能够接受 按键

    1
    2
    :execute "normal! gg/foo\<cr>dd"
    :execute "normal! mqA;\<esc>`q"

正则表达式

vim有四种不同的解析正则表达式的“模式”

  • 默认模式下\+表示“一个或多个之前字符”的正常+意义, 其他的符号如{,},*也都需要添加转义斜杠表示正常意义, 否则表示字符字面意

    • :execute "normal! gg/for .\\+ in .\\+:\<cr>: execute接受字符串,将\\转义,然后正则表达式解析, 查找pythonfor语句

    • :execute "normal! gg".'/for .\+ in .\+:'."\<cr>": 使用字符串字面量避免\\,但是注意此时\<cr>也不会 被转义为按下回车\n才是换行符),所以需要分开 书写、连接

  • \v模式下,vim使用“very magic”正常正则解析模式

    • :execute "normal! gg".'/\vfor .+ in .+:'."\<cr>"

VSCode基础

Settings

  • VSCode配置文件分为两种(其中项目会重复)

    • User Settings:用户对所有项目的配置
    • Workspace Settings:针对当前项目的设置
  • 配置方式分为两种完全相同

    • UI:提示丰富
    • JSON:配置快捷,JSON字典格式

Command Palette

Python

  • 选择默认python环境

Terminal

  • 选择默认terminal,可能的terminal包括cmd、powershell、 WSL(若启用Linux子系统)

Original Setting

Terminal

1
2
3
4
5
6
7
8
{
"terminal.integrated.shell.windows""/path/to/shell",
// 默认shell
"terminal.integrated.shellArgs.windows": [ ],
"terminal.integrated.shellArgs.linux": [ ],
"terminal.integrated.shellArgs.osx": [ ],
// VSCode创建terminal启动参数(3系统分别配置)
}
  • VSCode terminal分为很integrated、external

  • VSCode应该是兼容所有terminal shell,如

    • C:/Windows/System32/cmd.exe
    • C:/Windows/System32/powershell.exe
    • C:/Windows/System32/wsl.exe:WSL启用
    • “/path/to/git/bin/bash.exe”:Git中bash等
  • VSCode terminal虽然兼容多种shell,但只能创建默认shell

    • 需要多种shell只能切换默认shell再创建
    • python shell等shell是特殊shell,无法默认创建,必须要 在命令面板中创建(虽然在普通shell中打开python环境, 但是VSCode不认可)

Python

1
2
3
4
5
6
7
8
9
10
11
{
"python.condaPath": "/path/to/conda/Scripts",
// conda安装目录Scripts文件夹
"python.venvPath": "/path/to/conda/envs",
// 虚拟环境目录,VSCode会在其中查找虚拟环境,作为
// Command Palette中的备选项
"python.pythonPath": "/path/to/python.exe",
// 默认python解释器路径
"python.terminal.activateEnvironment": true,
// 创建python shell时,尝试中激活虚拟环境
}
  • python.terminal.activateEnviroment激活的虚拟环境由 python.pythonPath决定

    • VSCode会尝试执行python.pythonPath同级中 Scripts/activate.bat激活虚拟环境

    • 因此虚拟环境需要安装conda,否则没有 Scripts/ativate.bat无法正常激活默认虚拟环境

CPPC

配置文件

  • .vscode/c_cpp_properties.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    "configurations": [
    {
    "name": "WSL",
    "includePath": [
    "${workspaceFolder}/**"
    ],
    "defines": [
    "LOCAL",
    "_DEBUG",
    "UNICODE",
    "_UNICODE"
    ],
    "compilerPath": "/usr/bin/gcc",
    "cStandard": "c11",
    "cppStandard": "c++14",
    "intelliSenseMode": "gcc-x64"
    }
    ],
    "version": 4
    }
    • C/C++项目基本配置
  • .vscode/tasks.json:利用VSCode的Tasks功能调用WSL的 GCC/G++编译器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    {
    // tasks.json
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format

    "version": "2.0.0",
    "tasks": [
    {
    "label": "Build",
    "command": "g++",
    "args": [
    "-g",
    "-Wall",
    "-std=c++14",
    "/mnt/c/Users/xyy15926/Code/cppc/${fileBasename}",
    "-o",
    "/mnt/c/Users/xyy15926/Code/cppc/a.out",
    "-D",
    "LOCAL"
    ],
    "problemMatcher": {
    "owner": "cpp",
    "fileLocation": [
    "relative",
    "${workspaceRoot}"
    ],
    "pattern": {
    "regexp": "^(.*):(\\d+):(\\d+):\\s+(warining|error):\\s+(.*)$",
    "file": 1,
    "line": 2,
    "column": 3,
    "severity": 4,
    "message": 5
    }
    },
    "type": "shell",
    "group": {
    "kind": "build",
    "isDefault": true
    },
    "presentation": {
    "echo": true,
    "reveal": "silent",
    "focus": true,
    "panel": "shared"
    }
    },
    {
    "label": "Run",
    "command": "/mnt/c/Users/xyy15926/Code/cppc/a.out",
    "type": "shell",
    "dependsOn": "Build",
    "group": {
    "kind": "test",
    "isDefault": true
    },
    "presentation":{
    "echo": true,
    "reveal": "always",
    "focus": true,
    "panel": "shared",
    "showReuseMessage": true
    }
    }
    ]
    }
    • 这里为方便将运行程序任务同> Task: Run Test Task 任务关联,可以在命令面板执行此指令
  • .vscode/launch.json:gdb调试配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    {
    // launch.json
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
    {
    "name": "(gdb) Bash on Windows Launch",
    "type": "cppdbg",
    "request": "launch",
    "program": "/mnt/c/Users/xyy15926/Code/cppc/a.out",
    "args": ["-f", "Threading"],
    "stopAtEntry": false,
    "cwd": "/mnt/c/Users/xyy15926/Code/cppc/",
    "environment": [],
    "externalConsole": true,
    "MIMode": "gdb",
    "pipeTransport": {
    "debuggerPath": "/usr/bin/gdb",
    "pipeProgram": "C:\\windows\\system32\\bash.exe",
    "pipeArgs": ["-c"],
    "pipeCwd": ""
    },
    "setupCommands": [
    {
    "description": "Enable pretty-printing for gdb",
    "text": "-enable-pretty-printing",
    "ignoreFailures": false
    }
    ],
    "sourceFileMap": {
    "/mnt/c": "c:\\",
    "/mnt/d": "d:\\"
    },
    "preLaunchTask": "Build"
    },
    ]
    }

Git

1
2
3
4
{
"git.ignore.MissingGitWarning": true,
"git.path": "/path/to/xxxgit.exe"
}
  • “git.path”既可以是windows下Git,也可以是“伪装”Git,使用 工具wslgit,让VSCode 直接使用WSL内的Git

KeyMapper

  • <c-s-\>`:新建默认terminal绘画
  • <c-s-p>:command palette

Windows Linux Subsystem

说明

WSL是一个兼容层,类似反过来的Wine,但更底层

  • Linux、Windows程序不兼容,是因为二者内核提供的接口不同

    • ls/dir命令
      • 在Linux下调用getdents内核调用
      • 在Windows下调用NtQueryDirectoryFile内核调用
  • WSL提供Linux内核接口,并转换为NT内核接口

    • 在WSL中执行ls仍然调用getdents
    • WSL收到请求,将系统调用转换为NT内核接口 NTQueryDirectoryFile
    • NT内核收到WSL请求,返回执行结果
    • WSL将结果包装后返回
  • 毕相较于真正Linux系统仍然有不足

    • Docker等涉及未实现的内核特性软件如法使用
    • Raw socket相关相关操作容易出错
    • I/O性能相对孱弱

Cygwin对比

wsl_architecture

  • Cygwin提供了完整的POSIX系统调用API(以运行库 Cygwin*.dll形式提供,但是仍然工作在User Mode

    • Cygwin将POSIX系统调用转换为Win32 API(因为其架设在 Win32子系统上),很多内核操作(如:fork)受限于 Win32实现

    • Linux应用程序必须链接到Cynwin*.dll,需要修改源码 重新编译后才能执行,这样应用程序才不会直接请求内核, 而是调用Cygwin运行库,且编译出的可执行文件为 Win32 PE格式封装,只能在Windows下执行

  • WSL中Linux应用程序进程被包裹在Pico Process中,其发出的 所有系统调用会被直接送往Kernel Mode中的 lxcore.syslxss.sys

    • WSL将POSIX系统调用转换为更底层的NP API调用(WSL和 Win32平行,直接架设在NT内核上)

    • 可以直接执行ELF格式封装的Linux可执行程序

启用

  • 控制面板 ->
  • 程序和功能 ->
  • 启用或关闭windows功能 ->
  • 适用于Linux的Windows子系统

其他

使用

进入WSL

除在图形界面中点击图标,以默认参数启动,还可以在terminal (cmd/powershell等)自定义进入WSL参数

  • wsl.exe:打开默认发行版中默认shell
  • distroname.exe:打开指定发行版中默认shell
  • bash.exe:打开默认发行版中bash shell
  • 这些应用程序默认在path中,可以直接执行

版本管理

  • wslconfig.exe可以用于管理多个子系统的发行版

WSL、Windows互操作

文件

  • Windows所有盘符挂载在WSL中/mnt目录下

  • WSL中所有数据存放在%HOME%/AppData/Local/Packages/{linux发行包名}/LocalState/rootfs

    • 不要在Windows下直接修改,造成权限错误

命令

  • 在cmd中直接调用WSL命令
    1
    2
    PS> wsl [-e] ls -al
    // wsl带参数执行
  • 在WSL中调用Windows命令行程序(在$PATH中)

    1
    2
    $ which ipconfig.exe
    $ ipconfig.exe
  • 在WSL中启动Windows应用(在$PATH中)

    1
    $ notepad.exe
  • 通过pipes通信

    1
    2
    $ cat foo.txt | clip.exe
    PS> ipconfig | wsl grep IPv4

端口、环境变量

  • WSL与Windows共享端口(NT内核?)
  • WSL继承Windows的部分环境变量,如:PATH

Terminal推荐

  • wsl-terminal: 专为WSL开发的终端模拟器,基于minttywslbridge,稳定 易用

  • ConEmu:老牌终端模拟器, 功能强大

  • Hyper:基于Electron的跨平台 终端模拟器

WSL-Terminal

  • WSL-Terminal中包含一些快捷工具

    • tools目录中包含一些脚本,可以通过wscripts.exe 执行修改注册列表,添加一些功能
      • 添加WSL中vim、emacs等打开到右键菜单
      • 添加在WSL中打开文件夹到右键菜单
    • run-wsl-file.exe可以用于在快捷执行wsl脚本,只需要 将其选择为文件打开方式
    • vim.exe可以用WSL中vim打开任何文件,当然一般是配合 tools/中脚本在右键注册后使用
  • 配置

    • 配置文件etc/wsl-terminal.conf
    • 主题文件etc/themes/
    • mintty配置文件etc/mintty

其他问题

文件权限问题

  • 在WSL中,windows实现了两种文件系统用于支持不同使用场景

VolFs

VolFs:着力于在windows文件系统上提供完整的linux文件系统特性 ,通过各种手段实现了对InodesDirectory EntriesFile ObjectsFile DescriptorsSpecial File Types 的支持

  • 为支持Inodes,VolFS会把文件权限等信息保存在 NTFS Extended Attributes

    • 在Windows中新建的文件缺少此扩展参数,有些编辑器也会 在保存文件是去掉这些附加参数
    • 所以不要在Windows中修改WSL文件,否则VolFs无法正确 获得文件metadata
  • WSL中/就是VolFs文件系统

DrvFs

DrvFs:着力提供于Windows系统的互操作性,从Windows的文件权限 (即文件->属性->安全选项卡中的权限)推断出文件对应Linux权限

  • 所有windows盘符挂在在WSL中/mnt是都使用DrvFs文件系统

  • 由于DrvFs文件权限继承机制很微妙,结果就是所有文件权限 都是0777

    • 所以ls结果都是绿色的
    • 早期DrvFs不支持metadata,在Build 17063之后支持 文件写入metadata,但是需要重新挂载磁盘
  • 可以通过设置DrvFs metadata设置默认文件权限

    1
    2
    3
    4
    5
    $ sudo umount /mnt/e
    $ sudo mount -t drvfs E: /mnt/e -o metadata
    // 此时虽然支持文件权限修改,但默认权限仍然是*0777*
    $ sudo mount -t drvfs E: /mnt/e -o metadata,uid=1000,gid=1000,umask=22,fmask=111
    // 此时磁盘中默认文件权限为*0644*
  • 更合适的方式是通过/etc/wsl.conf配置DrvFs自动挂载属性

AutoMatically Configuring WSL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 # `/etc/wsl.conf`
[automount]
# 是否自动挂载
enabled = true
# 是否处理`/etc/fstab`文件
mountFsTab = true
# 挂载路径
root = /mnt/
# DrvFs挂载选项,若需要针对不同drive配置,建议使用`/etc/fstab`
options = "metadata,umask=023,dmask=022,fmask=001"
[network]
generateHosts = true
generateResolvConf = true
[interop]
# 是否允许WSL载入windows进程
enabled = true
appendWindowsPath = true
  • 如果需要给不同盘符设置不同挂载参数,需要修改 /etc/fstab

    1
    E: /mnt/e drvfs rw,relatime,uid=1000,gid=1000,metadata,umask=22,fmask=111 0 0

系统安装常识

Win10/8系统

在已经安装win10/8系统的机器上安装linux系统,需要注意

  • 电源设置中关闭快速启动,其有可能影响grub开机引导
  • boot中关闭secureboot

U盘启动盘

Win下usbwriter和ultraiso都可以制作,但是

  • usbwriter用于archlinux的制作
    • 打开
    • 启动->写入磁盘映像
    • 写入方式—USB-HDD+
  • ultraiso用于ubuntu和centos的制作

archlinux使用ultraiso和usbwriter制作u盘不同,用usbwriter的 u盘好像有隐藏分区,只能看到一个很小容量的盘,使用ultraiso 制作的启动盘好像没用

bootloader(启动引导器)

双系统根据需求选择

Windows使用EasyBCD引导Linux

EasyBCD

Windows下的一个引导器

NeoGrub是EasyBCD自带的grub(不是已经更加常用的grub2), 可以配置EasyBCD在无法找到其他Linux系统的grub的情况下, 将控制权转移给NeoGrub,使用其引导系统,这个NeoGrub就和 普通的grub类似,可以引导多个系统

  • 有很多文件系统格式无法识别,能够确认可识别的只有ext2, 不能的有xfsext4,其中ext4会被错认为ex2fs, 不能正确读取分区文件,
  • 据说只能识别标准格式的分区,无法识别lvm格式的分区

因此,如果需要使用NeoGrub引导系统,需要注意分区格式问题

分区

  • Ubuntu:”安装启动引导器的设备”设置为sdXY,即磁盘X的 Y分区,这样不会更改默认引导程序,此时会安装grub,但是 没有覆盖磁盘中默认的win引导,重启后会自动进入win,可以 使用easybsd自动检测引导分区,直接就能添加引导条目
  • Centos:选择不安装启动引导器grub(centos无法选择引导器 安装在某个分区),EasyBCD无法自动检测Linux的引导分区, 需要手动配置EasyBCD自带的NeoGrub,并添加此条目

引导文件编写

添加NeoGrub条目之后,其配置文件仍然为空,因此选择此条目之后 会直接进入grub控制台,在此尝试boot其他系统。

  • root (hdX,X表示磁盘代号,<tab>给出的候选分区, 确定boot分区Y

  • root (hdX,Y):指定根目录(中间有空格)

  • kernel /vmlinuz<tab>给出候选的内核文件,确定内核 文件(一般会有一个rescue的文件肯定不是)

  • kernel /vmlinuz---------- ro root=/dev/sdXY ro quite vga=791: 其中X不再是hd后面的数字而是字母,YrootY+1

  • initrd /initramfstab给出候选initrd镜像文件

  • initrd /initramfs---------

  • boot

如果成功进入系统,说明以上的命令可行,记录以上真正有效的 命令,据此修改NeoGrub配置文件

title name
    root (hdX,Y)
    kernel /vmlinuz------------ ro root=/dev/sdXY ro quite vga=791
    initrd /initramfs----------
    boot

可以在EasyBCD中打开配置文件,也可以直接修改C:/NST/menu.ls 文件

Linux使用grub引导Windows

grub可以引导包括win、linux在内的大部分系统,而且大部分教程 都是默认这种方式

  • Ubuntu:”安装启动引导器的设备”设置为sdX,即直接安装在 磁盘的最开始,修改默认引导
  • Centos:选择安装启动引导器
  • Archlinux:要手动安装os-prober(检测已安装系统)、grub, 配置grub启动文件,具体方法参见grub使用或是archlwiki

分区

  • /根分区:唯一必须分区

  • boot分区:一般建议单独给boot分区

    • 根分区位于lvm、RAID,或者是文件系统不能被引导程序 识别,单独的boot分区可以设为其他格式
    • 可以以只读方式挂载boot分区,防止内核文件被破坏
    • 多系统可以共享内核
  • swap分区

    • 如果设置swap分区,一般设置为内存的1~2倍
    • 建议使用交换文件代替,这样的比较灵活,而且如果内存 够用的话可以不启用swap文件,这样提升效率、保护硬盘

      • fallocate -l SIZE /SWAPFILENAME:创建交换文件 ,其中SIZE后面要跟单位(M、G)
      • chmod 600 /SWAPFILENAME:更改交换文件权限
      • mkswap /SWAPFILENAME:设置交换文件
      • swapon /SWAPFILENAME:启用一次交换文件

      或修改/etc/fstab文件,每次开机默认启用交换文件

      /SWAPFILENAME none swap defaults 0 0 /SWAPFILENAME swap swap sw 0 0

      前者是Arch教程,后者是Centos7教程,这两种写法应该 是一样的

GDB

概述

GDB是GNU发布的UNIX程序调试工具,可以帮助完成

  • 自定义运行程序
  • 让程序在指定断点处停止
  • 检查停止程序的内部状态
  • 动态改变程序执行环境

调试准备

  • gdb会根据文件名后缀确认调试程序的语言,设置gdb自身语言 环境,并随之改变语言环境

    • 如果程序由多种语言编译而成,gdb能根据不同语言自动 切换语言环境
  • 可以使用infoshowset命令查看设置当前语言环境

  • 可能会缺少glibc-debuginfo,无法显示全部调试信息

    • OpenSuse安装可能需要添加源

编译选项

  • 调试C++/C程序时,要求在编译时就把调试信息添加到可执行 文件中

    • 使用gcc/g++的-g参数添加源码信息
    • 否则调试时将看不见函数名、变量名,而是全部以运行时 内存地址代替
  • 关闭优化选项

    • 否则优化器会删改程序,使得某些变量不能访问、取值错误

启动GDB调试

  • gdb <exe>:直接gdb启动可执行文件<exe>
  • gdb <exe> core:用gdb同时调试可执行文件、core文件
  • gdb <exe> <PID>:给定进程PID,gdb自动attach该进程, 调试已经运行的程序
    • 也可不指定PID,直接关联源码,在gdb中使用attach命令 手动挂接,调试已运行程序

配置调试环境

源文件

  • gdb启动程序后,gdb会在PATH、当前目录中搜索程序的源文件 -directory/-d添加源文件搜索路径
  • 在gdb交互中使用dir[rectory] <dirname>指定源文件搜索 路径
  • gdb中使用list/l检查gdb是否能列出源码

程序运行环境

在gdb中run程序之前,可能需要设置、查看程序运行环境

  • 程序运行参数

    • set <args>:设置程序运行时参数
    • show <args>:查看已设置参数
  • 环境变量

    • path [<dir>]:查看、添加<dir>PATH
    • show paths:查看PATH
    • set environment var [=value]:设置环境变量
    • show environment var:查看环境变量
  • 工作目录

    • cd <dir>:等价于cd
    • pwd:等价于pwd
  • 输入输出

    • info terminal:显示程序所有终端模式
    • run > outfile:重定向程序输出
    • tty /dev/:指定输入、输出设备

参数

  • -s <file>/-symbols <file>:从指定文件读取符号表
  • -se <file>:从指定文件读取符号表信息,并用于可执行文件
  • -c <file>/-core <file>:调试core dump产生的core文件
  • -d <dir>/-directory <dir>:添加源文件搜索路径
    • 默认搜索路径是PATH

表达式

  • 表达式语法应该是当前所调试语言的语法

  • gdb另外还支持一些通用操作符

    • @:指定存储在堆上、动态分配内存大小的长度

      1
      2
      3
      4
      int *array = (int*)malloc(len * sizeof(int));
      # 源码
      > p *array@len
      # 打印数组`*array`
    • :::指定某个具体文件、函数中的变量

      • 常用于取出被隐藏的全局变量
    • [(type)]<addr>:内存地址addr处为一个type类型 变量

停止点

设置Breakpoint

break

break:在某位置设置断点

1
2
b[reak] [<probe_modifier>] [<location>] [thread <threadno>]
[if <condition>]
  • probo_modifier:命令处于probe point时需要
    • -probe:通用、自动推测探测类型
    • -probe-stapSystemTap探测
    • -probe-dtraceDTrace探测
  • location:设置断点位置
    • 缺省:当前栈帧中的执行位置
    • linespecs冒号分隔绝对位置参数(逐步精确)
      • 其中可以包括:文件名、函数名、标签名、行号
    • +-[n]标识相对当前行位置
    • *addr:在程序运行的内存地址addr
    • explicit:类似于linespecs,通过多个参数给出
  • threadno:设置断点的线程号
    • 缺省断点设置在所有线程上
    • gdb分配的线程编号,通过info threads查询
    • 多线程被gdb停止时,所有运行线程都被停止,方便 查看运行程序总体情况
  • if <condition>:满足条件condition时在断点处停止, 缺省立即停止程序
1
2
3
4
5
6
7
8
9
> break factorial.c:fact:the_top
# factorial.c文件、fact函数、the_top标签
> break +2
# 当前行之后两行
> break *main + 4
# `main`函数4B之后
> break -source factorial.c -function fact
-label the_top
# explicit方式,同linespecs
  • 标签:C++/C中配合goto使用(label_1:
  • <tab>:补全函数名称、函数原型(重载函数)
  • 若指定函数名称不唯一,gdb会列出函数原型供选择

tbreak

tbreak:设置一次性断点,生效后立刻被删除

rbreak

rbreak:对匹配正则表达式的行均设置断点

condition

condition:修改、设置断点n生效条件

1
2
> condition <bpnum> <condition>
# 设置断点`bpnum`生效条件为`condition`

ignore

ignore:设置忽略断点n次数

1
2
> ignore <bpnum> <count>`
# 忽略断点`bpnum` `count`次

设置Watchpoint

watch

watch:为某个表达式设置观察点

  • 观察某个表达式,其值发生变化时停止程序
1
2
> watch [-l|-location] <expr> [thread <threadno>]
[mask <maskvalue>]
  • -l:表示将expression视为地址,观察表达式指向的地址 中的值
  • expr:表达式、*开头表示的内地地址
  • threadno:同break
  • maskvalue:观察值mask,只观察部分bit值
1
2
3
4
> watch foo mask 0xffff00ff
# 观察`foo`
> watch *0xdeadbeef 0xffffff00
# 观察内存地址`0xdeadbeef`处的值
  • 取决于系统,观察点有可能以硬件、软件方式实现,大部分 PowerPC、X86支持硬件观察点
    • 软件观察点通过逐步测试变量实现,程序执行速度慢得多
    • 硬件观察点不会降低程序执行速度
  • mask参数需要架构支持

rwatch

rwatch:为某表达式设置读观察点

  • 当表达式值被读取时停止程序
1
2
> rwatch [-l|location] <expression> [thread <threadno>]
[mask <maskvalue>]

awatch

awatch:为某表达式设置写观察点

  • 当表达式值被修改时停止程序
1
2
> awatch [-l|-location] <expression> [thread <threadno>]
[mask <maskvalue>]

设置Catchpoint

catch

catch:设置捕捉点捕捉事件

1
> cat[ch] <event>
  • 通用事件
    • catch:捕获异常
    • exec:调用系统调用exec(仅HP-UX下有用)
    • fork:调用系统调用fork(仅HP-UX下有用)
    • load [<libname>]:载入共享库(仅HP-UX下有用)
    • unload [<libname>]:卸载共享库(仅HP-UX下有用)
    • throw:抛出异常
    • rethrow:再次抛出异常
    • vfork:调用系统调用vfork时(仅HP-UX下有用)
    • syscall:被系统调用
  • Ada
    • assert:捕获Ada断言失败
    • exception [arg]:捕获Ada和参数匹配的异常
    • handlers:捕获Ada异常
  • 捕捉点:捕捉程序运行时的一些事件,如:载入动态链接库、 异常事件<event>发生时停止程序

tcatch

tcatch:设置一次性捕捉点,程序停止后自动删除

信号处理

gdb可以在调试程序时处理任何信号,可以要求gdb在收到指定信号后 停止正在执行的程序以供调试

1
> handle <signal> <keywords>
  • signal:需要处理的信号
    • 信号可选使用SIG开头
    • 可以定义需要处理的信号范围SIGIO-SIGKILL
    • 可以使用all表示处理所有信号
  • keywords:被调试程序接收到信号后gdb的动作
    • nostop:不停止程序运行,打印消息告知收到信号
    • stop:停止程序运行
    • print:显示信息

      todo

    • noprint:不显示信息
    • noigore:gdb处理信号,交给被调试程序处理
    • nopass/ignore:gdb截留信号,不交给被调试程序处理

维护停止点

clear

clear:清除指定位置断点

1
> clear [<location>]
  • location:指定清除断点位置
    • 缺省:清除当前栈帧中正在执行行断点
    • 其余设置同break

commands

commands:设置断点生效时自动执行命令

  • 利于自动化调试
1
2
3
> commands [bpnum]
> ...command-list...
> end
  • bpnum:断点序号
    • 缺省:最近设置的断点
    • 5-7:指定断点区间

执行

文件

list

list:打印源代码

1
> l[ist] [<location>]
  • location:输入的源代码
    • 缺省/+:显示当前行后源代码
    • -:显示当前行前源代码
    • [[start], [end]]:显示范围内源代码(缺省表当前行)
    • 指定单行类似breaklocation参数
  • 一般默认显示10行,可以通过set listsize <count>设置

search/forward-search:从打印出最后行开始正则搜索源码

1
> search/forward-search <regexp>

reverse-search:从打印出的最后行反向正则搜索源码

1
> reverse-search <regexp>

directory

directory:指定源文件搜索路径

1
> dir[rectory] <dir>
  • dir:源文件路径
    • 可以指定多个搜索路径,linux下:分隔,windows下;
    • 缺省:清除自定义源文件搜索路径信息
  • show查看当前

继续执行

run

run:启动程序开始调试

1
> r[un] [<args>]
  • args
    • 缺省:上次runset args指定的参数
    • 参数中包含的*...将被用于执行的shell扩展 (需清除参数,使用set args置空)
  • 允许输入、输出重定向

continue

continue:继续执行直到之后断点、程序结束

1
> c[ontinue]/fg [<ignore-count>]
  • ignore-count:执行直到之后第ingore-count个断点 (忽略ignore-count-1个断点)
    • 缺省:1

step/next

step/next:单步/单行跟踪

1
> s[tep]/[n]ext [<count>]
  • count:执行代码行数
    • 缺省:1
  • 有函数调用,step进入函数(需要函数被编译有debug信息)

    next不进入函数调用,视为一代代码

stepi/nexti

stepi/nexti:单条intruction(机器指令、汇编语句)跟踪

1
> s[tep]i/n[ext]i [<count>]
  • count:执行指令条数
    • 缺省:1

finish

finish:运行直到当前栈帧/函数返回,并打印函数返回值、存入 值历史

return

return:强制函数忽未执行语句,并返回

1
> return [expr]
  • expr:返回的表达式值
    • 缺省:不返回值

util

until/u:运行程序直到退出循环体

jump

jump:修改程序执行顺序,跳转至程序其他执行处

1
> jump [<location>]
  • location:同break
  • jump不改变当前程序栈中内容,所以在函数间跳转时,函数 执行完毕返回时进行弹栈操作式必然发生错误、结果错误、 core dump,所以最好在同一个函数中跳转
  • 事实上,jump就是改变了寄存器中保存当前代码所在的内存 地址,所以可以通过set $pc更改跳转执行地址

call

call:强制调用函数,并打印函数返回值(void不显示)

1
> call <expr>
  • print也可以调用函数,但是如果函数返回voidprint 显示并存储如历史数据中

查看信息

1
> p[rint] [/<f>]<expr>[=value]
  • f:输出格式
    • x:16进制格式
    • d:10进制格式
    • u:16进制格式显示无符号整形
    • o:8进制格式
    • t:2进制
    • a:16进制
    • c:字符格式
    • f:浮点数格式
    • i:机制指令码
    • s
  • expr:输出表达式、gdb环境变量、寄存器值、函数
    • 输出表达式:gdb中可以随时查看以下3种变量值
      • 全局变量:所有文件可见
      • 静态全局变量:当前文件可见
      • 局部变量:当前scope可见
      • 局部变量会隐藏全局变量,查找被隐藏变量可以使用 ::指定
    • 编译程序时若开启优化选项,会删改程序,使得某些变量 不能访问
    • 输出环境变量、寄存器变量时,需要使用$前缀
    • 函数名称:强制调用函数,类似call
  • value:修改被调试程序运行时变量值
    • 缺省:打印变量值
    • =是C++/C语法,可以根据被调试程序改为相应程序赋值 语法
    • 可以通过set var实现(当变量名为gdb参数时,必须 使用set var
  • 每个print输出的表达式都会被gdb记录,gdb会以$1$2 等方式记录下来,可以使用此编号访问以前的表达式

examine

examine/x:查看内存地址中的值

1
> examine/x /[<n/f/u>] <addr>
  • 输出参数:可以三者同时使用
    • n:查看内存的长度(单元数目
    • f:展示格式,同print
      • u:内存单元长度
      • b:单字节
      • h:双字节
      • w:四字节,默认
      • g:八字节
  • addr:内存地址
1
2
> x/3uh 0x54320
# 从内存地址`0x54320`开始,16进制展示3个双字节单位

display

display:设置自动显示变量,程序停止时变量会自动显示

1
> display/[<fmt>] [<expr>] [<addr>]
  • fmt:显示格式
    • print
  • exprt:表达式
  • addr:内存地址
1
2
3
> display/i $pc
# `$pc`:gdb环境变量,表示指令地址
# 单步跟踪会在打印程序代码时,同时打印出机器指令

undisplay

undisplay:删除自动显示

1
> undisplay [<num>]
  • num:自动显示编号
    • info查看
    • 可以使用a-b表示范围

查看、设置GDB环境

info

停止点

  • locals:打印当前函数中所有局部变量名、值
  • args:打印当前函数参数名、值
  • b[reak][points] [n]:查看断点
  • watchpoints [n]:列出所有观察点
  • catch:打印当前函数中异常处理信息
  • line [<location>]:查看源代码在内存中地址
  • f[rame]:可以打印更详细当前栈帧信息
    • 大部分为运行时内存地址
  • display:查看display设置的自动显示信息

线程

  • threads查看在正在运行程序中的线程信息

信号

  • info signals/handle:查看被gdb检测的信号
  • frame:查看当前函数语言
  • source:查看当前文件程序语言

其他

  • terminal:显示程序所有终端模式
  • registers [reg]:查看寄存器情况
    • 缺省:除浮点寄存器外所有寄存器
    • 还可以通过print实现
  • all-registers:查看所有寄存器情况(包括浮点寄存器)

set

停止点

  • step-mode [on] [off]:开启/关闭step-mode模式
    • 程序不会因为没有debug信息而不停止,方便查看机器码

环境

  • language [lang]:设置当前语言环境
  • args [<args>]:设置被调试程序启动参数
  • environment var [=value]:设置环境变量
  • listsize <count>:设置最大打印源码行数
  • var <var=value>:修改被调试程序运行时变量值
    • 还可以通过print变量修改

print

  • address [on/off]:打开地址输出

    • 即程序显示函数信息时,显示函数地址
    • 默认打开
  • array [on/off]:打开数组显示

    • 打开数组显示后,每个函数占一行,否则以逗号分隔
    • 默认关闭
  • elements <num-of-elements>:设置数组显示最大长度

    • 0:不限制数组显示
  • null-stop [on/off]:打开选项后,显示字符串时遇到结束符 则停止显示

    • 默认关闭
  • pretty [on/off]:打开选项后,美化结构体输出

    • 打开选项后,结构体成员单行显示,否则逗号分隔
  • sevenbit-strings [on/off]:字符是否按照/nnn格式显示

    • 打开后字符串/字符按照/nnn显示

      todo

  • union [on/off]:显示结构体时是否显示其内联合体数据

    • 打开时联合体显示结构体各种值,否则显示...
  • object [on/off]:打开选项时,若指针对象指向其派生类, gdb自动按照虚方法调用的规则显示输出,否则gdb忽略虚函数表

    • 默认关闭
  • static-members [on/off]:是否对象中静态数据成员

    • 默认打开
  • vtbl [on/off]:选项打开,gdb将用比较规则的格式输出 虚函数表

    • 默认关闭

show

执行

  • args:查看被调试程序启动参数
  • paths:查看gdb中PATH
  • environtment [var]:查看环境变量
  • directories:显示源文件搜索路径
  • convenience:查看当前设置的所有环境变量
  • address:查看是否打开地址输出
  • array:查看是否打开数组显示
  • element:查看再打数组显示最大长度
  • pretty:查看是否美化结构体输出
  • sevenbit-strings [on/off]:查看字符显示是否打开
  • union:查看联合体数据输出方式
  • object:查看对象选项设置
  • static-members:查看静态数据成员选项设置
  • vtbl:查看虚函数显示格式选项设置

shell

shell:执行shell命令

1
> shell <shell-cmd>
  • cd:等同> shell cd
  • pwd
  • make <make-args>
  • Linux:使用环境变量SHELL/bin/sh执行命令
  • Windows:使用cmd.exe执行命令

path

path:添加路径至gdb中PATH(不修改外部PAHT

1
> path <dir>

GDB环境

环境变量

可以在gdb调试环境中自定义环境变量保存调试程序中需要的数据

1
2
3
4
5
6
7
8
> set $foo = *object_ptr
# 设置环境变量
> show convenience
# 查看当前设置的所有环境变量
> set $i=0
> print bar[$i++] -> contents
# 环境变量、程序变量交互使用
# 只需要回车重复上条命令,环境变量自动累加,逐个输出变量
  • 环境变量使用$开头(定义时也需要)
  • gdb会在首次使用时创建该变量,在以后使用直接对其赋值
  • 环境变量没有类型,可以定义任何类型,包括结构体、数组

寄存器

寄存器:存放了程序运行时数据

  • ip:程序当前运行指令地址
  • sp:程序当前堆栈地址
1
2
3
4
5
6
> info registers [<reg-name>]
# 输出寄存器值,缺省除浮点外所有
> info all-registers
# 输出所有寄存器值
> print $eip
# 输出寄存器`eip`值

其他

  • disassemble:查看程序当前执行的机器码
    • 此命令会dump当前内存中指令
  • si[gnal] <signal>:产生信号量发给被调试程序
    • signal:取值1-15,即Unix信号量
    • 此命令直接发送信号给被调试程序,而系统信号则是发送给 被调试程序,但由gdb截获

调试设置

delete

delete:删除断点(缺省)、自动输出表达式等

1
2
> delete [breakpoints] [bookmark] [checkpoints]
[display] [mem] [tracepoints] [tvariable] [num]
  • breakpoints:删除断点
  • bookmark:从书签中删除书签
  • checkpoints:删除检查点
  • display:取消程序停止时某些输出信息
  • mem:删除存储区
  • tracepoint:删除指定追踪点
  • tvariable:删除追踪变量

  • num

    • 缺省:删除所有断点/自动输出/书签等
    • 指定的序号
      • info查看
      • 可以使用a-b表示范围

disable

disable:禁用断点(缺省)、输出表达式等

1
2
3
> disable [breakpoints] [display] [frame-filter]
[mem] [pretty-printer] [probes] [type-printer]
[unwinder] [xmethod] [num]

breakpoints

禁用断点

1
> disable [breakpoints] [num]
  • 缺省:禁用所有断点
  • 仅指定的序号(info查看)

display

禁用程序停止时某些输出信息

frame-filter

禁用某些帧过滤器

mem

禁用存储区

pretty-printer

禁用某些打印美化

probes

禁用探测

type-printer

禁用某些类型打印

unwinder

禁用某些unwinder

xmethod

禁用某些xmethod

enable

enable:启用断点(缺省)、输出表达式等

1
2
3
> enable [breakpoints] [display] [frame-filter]
[mem] [pretty-printer] [probes] [type-printer]
[unwinder] [xmethod] [num]

breakpoints

启用断点

1
2
> enable [breakpoints] [num] [once] [delete]`
- `[delete]`:启用断点,生效后自动被删除
  • num:断点序号
    • 缺省:启用所有断点
  • once:启用断点一次
  • delete:启用生效后自动删除
  • count:启用断点count

display

启用程序停止时某些输出信息

frame-filter

启用某些帧过滤器

mem

启用存储区

pretty-printer

启用某些打印美化

probes

启用探测

type-printer

启用某些类型打印

unwinder

启用某些unwinder

xmethod

启用某些xmethod

backtrace

backtrace:打印函数栈

1
> backtrace/bt [-][<n>]
  • -:打印栈底信息
    • 缺省:打印栈顶信息
  • n:打印栈数量
    • 缺省:打印当前函数调用栈所有信息
  • 一般而言,程序停止时,最顶层栈就是当前函数栈

frame

frame:切换当前栈

1
> f[rame] [<n>]
  • n:切换到第n个栈帧
    • 缺省打印当前栈编号、断点信息(函数参数、行号等)

up

up:上移当前栈帧

1
> up [<n>]
  • n:上移n层栈帧
    • 缺省:上移1层

down

down:下移当前栈帧

1
> down [<n>]
  • n:下移n层栈帧
    • 缺省:下移1层

GDB命令大全

aliases

breakpoints

data

files

internals

obscure

running

stack

status

support

tracepoints

user-defined

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ gdb tst
# `gdb`启动可执行文件tst
(gdb) l
# `l`:list,列出源码
(gdb)
# 直接回车:重复上次命令
(gdb) break 16
# `break [n]`:在第`n`行设置断点
(gdb) break func
# `break func`:在函数`func`入口处设置断点
(gdb) info break
# `info break`:查看断点信息
(gdb) r
# `r`:run,执行程序,会自动在断点处停止
(gdb) n
# `n`:next,单条语句执行
(gdb) c
# `c`:continue,继续执行(下个断点、程序结束为止)
(gdb) p i
# `p i`:print,打印变量i的值
(gdb) bt
# `bt`:查看函数栈
(gdb) finish
# `finish`:退出**函数**
(gdb) 1
# `q`:quit,退出gdb