GDB 7.0 中文手册 —— 4. 在GDB中运行程序

部署运行你感兴趣的模型镜像

GDB最新的手册,每天翻译一些。

虽水平有限,望造福大家

转贴请注明出处:

http://blog.youkuaiyun.com/benson_linux/archive/2009/11/14/4811577.aspx


英文原文:
http://sourceware.org/gdb/current/onlinedocs/gdb_toc.html

 


 

4. 在GDB中调试程序

 

如果想在GDB下调试一个程序,那个编译这个程序时,需要生成调试信息。根据需要,我们可以带参数或不带参数的启动GDB,如果是本地调试,你可以重定向被调试程序的输入输出,或调试一个已经运行的程序,还可以杀掉子进程。

  • Compilation:编译时加入调试信息
  • Starting:启动你的待调试程序
  • Arguments:待调试程序的参数
  • Environment:待调试程序的运行环境
  • Working Directory:待调试程序的工作目录
  • Input/Output:待调试程序的输入输出
  • Attach:调试一个正在运行的程序
  • Kill Process:杀掉子进程
  • Inferiors and Programs:Debugging multiple inferiors and programs
  • Threads:调试多线程程序
  • Forks:调试forks
  • Checkpoint/Restart:设置书签以便之後返回

 

 

4.1 编译时加入调试信息

 

为了让程序能够有效的调试,编译时我们需要加入调试信息。这些调试信息保存在目标文件中(object file);包含每个变量或函数的数据类型,和执行码与源代码之间的相应位置。

 

怎么加入调试信息呢?编译的时候加上‘-g’选项就行了。

 

那些将要发行的程序,一般都会使用‘-O’选项进行编译优化。然而,一些编译器不能同时支持'-g'和’-O’选项,使用这些编译器时,我们无法得到既能够调试,又是经过优化的执行程序。

 

GCC,GNU的C/C++编译器,就能够同时支持'-g'和'-O'选项,这样我们就可以得到支持调试的经过优化的执行文件。所以我们建议不论任何时候编译,都加上'-g‘选项。毕竟程序往往不能像我们想的那么顺利运行。更多信息,参考Optimized Code (代码优化)。

 

老版本的GNU C编译器,支持一种变形的调试信息选项'-gg'。GDB不再支持这种格式了,不要在使用了。

 

GDB知道代码中的预处理宏和对应的展开式(参考Macros )。对于大部分编译器来说,仅仅指定'-g'标志,并不会在调试信息中包含宏信息,因为它们太庞大了。3.1以后的GCC版本,如果编译时指定了'-gdwarf-2’和'-g3‘,会加入这些宏的信息。调试信息格式要求是Dwarf 2的,并且第二个选项还要求额外信息。将来,希望能够找到更合适的方法来表示宏信息,这样就能用'-g’选项来搞定一切了。

 


 

 

4.2 启动待调试程序

 

run

r

在GDB中用run命令启动被调试程序,我们首先需要通过GDB参数(参考Getting In and Out of GDB ),或者file或exec-file命令(参考Commands to Specify Files )给出被调试程序的名字(VxWorks平台除外)。

如果你在一个支持进程的环境中运行程序,run命令会创建一个子进程运行被调试程序。在某些没有进程的环境中,run命令跳到被调试程序的开始处。像'remote'一样的其他目标则总是运行。如果你收到这样的错误信息:

     The "remote" target does not support "run".
     Try "help target" or "continue".

那就用continue命令来运行被调试程序。当然你可能需要先做load(参考load )。

 

程序的运行会受父进程传入的信息的影响。GDB提供了多种方法来设置这些信息,当然你必须在启动被调试程序之前设置。(我们也可以在启动之后设置,不过这要等到重启程序时才有效。)这些信息可以分为4类:

 

The arguments

指定给被调试程序的参数可以作为 run 命令的参数。如果你的目标环境中有shell,那么shell会被用来传递参数,所以那些shell支持的常规用法(像通配符、变量替换),这里也可以用。在Unix系统中,可以通过SHELL环境变量设置使用那个shell。参考Your Program's Arguments

The environment

通常被调试程序会继承GDB的环境变量,不过我们也可以用GDB的“set environment”和“unset environment”命令来修改部分影响被调试程序运行的环境变量。参考Your Program's Environment

The working directory

被调试程序会继承GDB的工作目录。我们也可以用GDB的cd命令设置GDB的工作目录。参考Your Program's Working Directory

The standard input and output

通常被调试程序会和GDB使用同样的标准输入输出设备。我们也可以在run命令行中,重定向输入输出,或者使用tty命令设置被调试程序自己的终端设备。参考Your Program's Input and Output

 

警告:当输入输出重定向起作用时,你不能用管道将被调试程序的输出连接到另外一个程序;如果你这么做了,不管你的程序是否产生输出,GDB里的被调试程序都会马上退出,并且exit code非零。例如,命令“run | more”就会产生这样的错误(并不是你的程序真的有问题)。

一旦你执行了run命令,你的被调试程序就会立即开始运行。参考Stopping and Continuing ,了解如何停止程序。一旦你的程序停止运行,就可以通过print或者call命令,调用被调试程序中的函数。参考Examining Data

 

GDB读入符号后,如果你的符号文件的修改时间发生了变化,GDB将会丢弃已有的符号表,然后重新读入并更新。当然,GDB干这些活时,它会尽量保留你当前的断点信息。

 

start

主函数的名字随着语言的不同而不同,在C/C++中,主函数的名字一般都是main,不过在Ada等其他语言中,并不要求主函数用一个固定的名字。对于不同的语言,调试器提供了很简单的方法启动被调试程序,和在主函数的第一条指令处停止运行。

 

“start”命令等同于在主函数的开始加入一个临时断点,然后执行“run”命令。

 

某些程序包含一些初始化代码段,这些代码段早于主函数运行,当然这些都取决于你使用的编程语言。例如,在C++中,静态变量和全局对象的构造函数就在主函数之前运行,因此也就有可能使调试器在主函数之前就产生中断。无论如何临时断点都会保留以便暂停被调试程序的运行。

 

通过给‘start’命令指定参数,可以设置被调试程序的参数。这些参数将会原封不动的传递给‘start’命令背后的‘run’命令。注意,如果之后使用的'start'或者‘run'参数不带参数的话,已经设置的参数将会默认使用。

 

当需要调试程序的初始化代码段时,使用’start‘命令中断程序可能有些来不及,那么干脆就程序运行之前,在你的初始化代码段中加入断点。(貌似C++代码不能调试构造函数?)

set exec-wrapper wrapper

show exec-wrapper

unset exec-wrapper

设置了'exec-wrapper'(还是保留吧,译不好,我想叫做执行程序外壳)时,指定的外壳程序将会引导被调试程序的运行。GDB通过'exec wrapper program '的shell方式启动你的程序(这里wrapper 就是那个指定的外壳程序,program 就是你的被调试程序)。GDB会自动给program 和它的参数加上引号,但不给wrapper 加,所以shell需要的话,你得自己给wrapper 加上引号。wrapper程序会一直运行到被调试程序启动,然后GDB接手控制权。

 

我们可以使用任何最终调用execve(以传入的参数,作为execve的参数)系统函数并推出的程序作为外壳。很多标准就是例子,如env和nohup。任何以'exec "$@"'结束的Unix shell脚本也行。

 

举个例子理解一下,我们可以用env来给被调试程序传入环境变量,而不用在我们的shell中设置环境变量:

本命令适用于很多平台的本地调试,包括DJGPP,Cygwin,MS Windows,和QNX Neutrino。

set disable-randomization

set disable-randomization on

gdb默认会开启这个选项,来关闭调试程序的内存虚拟地址随机排布的特性。本选项对于同时启动多个一样的调试会话很有用,能优化被调试程序的多次启动,提高它们之间的内存空间的复用。(内存地址随机排布的内容,可以参考这篇贴子:http://blog.youkuaiyun.com/drshenlei/archive/2009/07/11/4339110.aspx,很不错)

 

这个功能只在GNU/Linux平台有效,使用下边的命令也能获得同样效果:

 

set disable-randomization off

保持启动的被调试程序的加载方式。有些bug只会在执行程序加载到特定地址的时候才会出现。如果你的bug在GDB调试环境中运行时不见了,就很有可能是因为在一些平台上,GDB默认的禁止了地址空间的随机排布。例如GNU/Linux上,系统为每个独立的进程采用内存地址随机排布的方式。试试用‘set disable-randomization off’来重现这种bug。

 

目前只有在GNU/Linux平台上实施了虚拟地址空间随机使用技术,这有助于避免进程遭到安全性攻击。这种情况下,攻击者需要知道具体执行码的准确位置。随机化分布执行执行码地址后,基本不可能轻易的插入跳转语句,执行一段恶意代码。

 

提前链接动态库提高了程序启动的速度。不过这也使特权进程在这些库中的地址变得可以预测,很简单,只要在目标系统上让非特权的进程访问这些动态库。解析这些动态库的二进制文件能够获得足够的信息,来汇编恶意代码并运行它们,就算程序启动时执行一个常规的重定向处理,就能使一个预链接的动态库重新载入一个随机地址。没有预链接的动态库总是会被加载到一个随机选择的地址。

 

位置无关可执行程序(PIE)含有位置无关代码,有点像动态库,因此启动时这些代码会被随机加载到一个地址上。就算是预链接的动态库,PIE执行程序通常也会把它们加载到一个随机的地址上。通过使用“gcc -fPIE -pie”可以编译得到这样的程序。

 

堆(malloc申请的空间),栈和程序中的mmap区域通常都是随机分配的地址(只要激活了随机化处理)

show disable-randomization

打印当前配置中,已启动程序中虚拟地址空间的随机化功能的详细禁止信息。

 


 

4.3 待调试程序的参数

 

待调试程序的参数可以通过GDB的命令行来指定。这些命令行参数会先传入一个shell中,处理通配符和I/O重定向操作,并从shell中将处理后的参数传入待调试程序。SHELL变量的值(如果有的话)用来指定gdb中使用的shell。如果你的环境中没有定义SHELL变量,gdb使用默认的shell(/bin/sh 在Unix系统中)。

 

在非Unix系统上,程序往往被gdb直接调用,并通过相应的系统调用执行I/O重定向,通过待调试程序的启动代码展开通配符,而不是shell。

 

不带参数的run命令会传入上次运行run命令时的参数,或者通过set args命令设置的参数。

set args

指定下次待调试程序运行时的参数。如果set args没有参数,那么运行待调试程序时也没有参数。一旦你通过run启动待调试程序时添加了参数,那么在下次run之前使用set args是唯一的让待调试程序不带参数启动的方法。

 

show args

打印程序启动时将传入的参数。

 


 

4.4 Your Program's Environment

 

SHELL环境由一系列环境变量名值对组成。环境变量能很方便的记录你的用户名、主目录、终端类型和命令搜索路径这样的配置信息。一般情况下,你可以在shell中设置这些环境变量,然后让shell中运行的程序继承这些变量,这对于调试程序的就很有用了,不需要重启gdb,直接修改环境变量,然后再次运行被调试程序就能生效。

path directory
    Add directory to the front of the PATH environment variable (the search path for executables) that will be passed to your program. The value of PATH used by gdb does not change. You may specify several directory names, separated by whitespace or by a system-dependent separator character (`:' on Unix, `;' on MS-DOS and MS-Windows). If directory is already in the path, it is moved to the front, so it is searched sooner.

    You can use the string `$cwd' to refer to whatever is the current working directory at the time gdb searches the path. If you use `.' instead, it refers to the directory where you executed the path command. gdb replaces `.' in the directory argument (with the current path) before adding directory to the search path.


show paths
    Display the list of search paths for executables (the PATH environment variable).


show environment [varname]
    Print the value of environment variable varname to be given to your program when it starts. If you do not supply varname, print the names and values of all environment variables to be given to your program. You can abbreviate environment as env.


set environment varname [=value]
    Set environment variable varname to value. The value changes for your program only, not for gdb itself. value may be any string; the values of environment variables are just strings, and any interpretation is supplied by your program itself. The value parameter is optional; if it is eliminated, the variable is set to a null value.

    For example, this command:

              set env USER = foo
         

    tells the debugged program, when subsequently run, that its user is named `foo'. (The spaces around `=' are used for clarity here; they are not actually required.)


unset environment varname
    Remove variable varname from the environment to be passed to your program. This is different from `set env varname ='; unset environment removes the variable from the environment, rather than assigning it an empty value.

Warning: On Unix systems, gdb runs your program using the shell indicated by your SHELL environment variable if it exists (or /bin/sh if not). If your SHELL variable names a shell that runs an initialization file—such as .cshrc for C-shell, or .bashrc for BASH—any variables you set in that file affect your program. You may wish to move setting of environment variables to files that are only run when you sign on, such as .login or .profile. 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

文件版本控制工具git使用 1. 实验类型 验证型,必做实验 2. 实验目的 掌握源代码版本控制工具git的基本使用,理解源代码版本控制的原理; 3. 题目描述 通过一个简单的测试项目的源代码版本控制,实践git的使用,掌握源代码版本控制的意义。 4. 实验要求 基本层次 能使用git工具完成从服务器上将源代码导出,将一个简单的测试项目的代码导出,并完成代码的修改和提交。 提高层次 能解决文件版本冲突问题。 5. 相关知识 Linux环境C程序开发的工具有: 编辑器(vi, emacs等) 编译程序(gcc, make等) 调试程序gdb等) 版本控制程序(git, svn等) 帮助文件生成(groff等) 软件打包工具(tar, rpm等) 等等,本实验以版本控制程序为例子,对开源软件开发方法进行介绍。 源代码版本控制软件是实现软件配置管理(Software Configuration Management, SCM)的重要工具。软件配置管理的主要目的是实现了团队协作、过程管理、并以透明的方式管理软件产品的开发,以实现提高开发速度、追踪开发过程、提高开发质量的目标。 版本控制软件可以实现软件的多个版本的可跟踪、可管理性。图1是一个简单的软件项目的开发过程的版本模型。图中,方框表示软件项目的不同版本,软件项目的一个版本包含了项目的所有源代码和其他文件;数字表示版本号码。Branch(分支)是开发者所开发的软件代码,通常branch是软件项目中一些具体的功能,比如branch 2-3可能是一个管理软件的用户登录模块,branch 7-8可能是用户帐号管理模块;Trunk(主干)包含了所有branches的最早的版本,是每个branche的开始;Tag(标签)是主干中实现了预期阶段功能(称为里程碑(milestone))的版本;Merge(合并)是开发者将branch版本和trunk版本相融合,是将branch版本的功能添加到trunk版本中的过程;Branch的开发可能结束,例如版本16。Trunk上的版本合并了branches开发的功能,是一些具有完整功能的软件版本,比如版本4合并了用户登录模块,版本10又合并了用户帐号管理模块,这样版本10就具有用户登录模块和用户帐号模块,版本3不具有用户帐号管理模块,版本8不具有用户登录模块。当然,开发者还可以在branch2-3上工作,继续改进登录模块,再合并到trunk中版本10之后的版本中,从而改进整个软件项目,而登录模块的开发也不受其他模块的影响。 一个简单的软件开发版本模型 图1 一个简单的软件开发版本模型 现代软件开发一般是多人的、分布式的开发形式,例如:用户A和用户B可能同时对同一软件项目test中的文件main.c进行了修改,在用户A、B将main.c的修改保存到中心版本库时,需要解决同步的问题。大多数的版本控制软件是采用的是“拷贝-修改-合并”模型(本部分介绍请参考文献[1]中“SVN手册”链接)来解决同步问题,在这种模型里,每一个客户连接项目版本库建立一个个人工作拷贝,也就是版本库中文件和目录的本地映射。用户并行工作,修改各自的工作拷贝,最终,各个私有的拷贝合并在一起,成为最终的版本,这种系统通常可以辅助合并操作,但是最终要靠人工去确定正误。 图2和图3是一个例子(摘自参考文献[1] 中“SVN手册”链接),展示了用户Harry和Sally对代码库(Repository)中文件A的“拷贝-修改-合并”过程:图3-2中Harry和Sally分别拷贝A,生成各自的工作拷贝;然后Harry和Sally分别对A做了修改,修改后对应文件为A’和A”,Sally将A”传送到代码库中,当Harry将自己的修改A’上传时被提示“过时”错误。 “拷贝-修改-合并”模型 图2 “拷贝-修改-合并”模型 接下来,如图3所示,Harry比较代码库中A的新版本A”与自己修改版本的差异,手工修改差异生成文件A*,这个过程叫“合并”,此后,Harry可以将合并后的A*上传到代码库中。Sally以后就可以取出最新的A*文件了。这个过程最核心的部分是人工的“合并”过程,合并必须通过Harry和Sally的交流来确定。 “拷贝-修改-合并”模型(续) 图3 “拷贝-修改-合并”模型(续) Git是一个开源的分布式版本控制软件,是由Linux之父Linus Torvalds为Linux内核维护工作在2005年开发的源代码版本控制软件。自诞生以来,Git 就以其开源、简单、快捷、分布式、高效等特点,应付了类似 Linux 内核源代码等各种复杂的项目开发需求。如今,Git 已经非常成熟,被广泛接受与使用,越来越多的项目都迁移到 Git 仓库中进行管理。 Git的设计核心是分布式控制管理,目的是为了摆脱对中心仓库的依赖,以支持离线工作。例如(该例来源于IBM developerWorks的“开源分布式版本控制工具 —— Git 之旅”):一个由Alice、Bob、Clair、David 四名成员组成的项目组。其中,除了中心仓库 origin(Git 默认远程仓库名称)之外,每一名成员各自负责一个完整的本地仓库(或者称作本地工作拷贝)。从分布式的观点来看,David 可看成是 Alice 的远程仓库,反过来也是一样。其分布式工作如图4所示。 git分布式工作示意 图4 Git的分布式工作示意 Git拥有强大的分支特性,主要提供四种工作流(来源, 出自atlassian Git Tutorials): Centralized Workflow 仅有一个主干,没有分支。 Feature Branch Workflow 在开发每个功能时都应该创建一个独立的分支而不只是使用主干。由于每个分支是独立且互不影响,这就意味着主干不会包含分支的修改。 Gitflow Workflow 使用独立的分支来准备发布(preparing),维护(maintaining), 和记录版本(recording releases)。 Forking Workflow 每个开发者都有两个远程仓库:远程私有的仓库和远程共享的仓库。 为了验证两个开发人员使用Git客户端通过远程中心代码库进行版本控制的过程,介绍了两个解决方案:方案一将建立本地访问方式的远程中心代码库,这个代码库与通过网络方式访问远程中心代码库从操作上看几乎没有区别(本次实验内容);方案二使用Gitlab作为远程中心代码库,就是真实的分布式开发的情景(下次实验内容)。 6. 实验设备 实验者需要使用浏览器软件(建议使用Chrome或Firefox),访问实验平台(地址)完成实验。 实验环境为“Oracle Linux 7.4”。 本次实验需要使用字符界面,操作为:点击桌面终端图标名为“Xfce终端”,打开Terminal 7. 实验指导 7.1 Git命令介绍 Git的功能主要由git命令实现,其命令行格式为: git <subcommand> [options] [args] 其中subcommand为子命令,主要包括: help add commit push pull diff fetch checkout merge rebase info log status 等。 例如,help子命令用于获取帮助信息: git help add 请打开命令行窗口使用上述命令,该命令打印出了add子命令的帮助信息。 实验环境中会出现如下错误: 没有 git-add 的手册页条目。 这是实验环境限制,目前没有找到处理的方法。请访问官网上的版主文档(https://git-scm.com/docs)。 Git采用了本地工作目录、暂存、本地代码库和远程代码库实现“拷贝-修改-合并”模型,其存储过程如图6所示: git存储过程 图6 Git的存储过程 图中,箭头符号中的文字为git的子命令,表示git所进行的操作。 下面,举一个例子来说明Git本地代码库的存储过程。 首先,新建一个空的项目。 cd /root/workspace ls -l total 168 drwxr-xr-x 1 1000 1000 170 Oct 21 00:55 example -rw-r--r-- 1 root root 170060 Oct 21 00:55 fping_4.0.orig.tar.gz drwxr-xr-x 1 root root 319 Oct 21 00:55 shell mkdir proj 上述命令为git创建一个专用空目录,作为工作目录(也叫工作拷贝)。 ls -l total 168 drwxr-xr-x 1 1000 1000 170 Oct 21 00:55 example -rw-r--r-- 1 root root 170060 Oct 21 00:55 fping_4.0.orig.tar.gz drwxr-xr-x 1 root root 10 Oct 21 00:55 proj drwxr-xr-x 1 root root 319 Oct 21 00:55 shell cd proj git init Initialized empty Git repository in /root/workspace/proj/.git/ git init是创建本地代码库的命令,命令行形式为: git init [-q | --quiet] [--bare] [--template=<template_directory>][--separate-git-dir <git dir>][--shared[=<permissions>]] [directory] 参数directory表示创建代码库的同时创建目录,即省略创建目录的操作。上述过程创建代码库的操作中(第6个命令行),若执行"git init 目录名"命令,则不需要创建目录的操作(第3个命令行)。 选项–bare用于创建远程代码库(登录远程服务进行操作时使用),不使用这个选项表示创建本地代码库。后面使用中用来创建模拟的远程代码库。 可以通过git help init来查看子命令的帮助信息。(实验环境结果错误请参考前述说明) 以上命令创建了一个本地代码库,其实是一个隐藏文件夹,位于本地工作目录中,这里是/root/workspace/proj/.git。本地代码库中的文件与工作目录的文件是不一样的,见稍后对远程代码库的介绍。 注意:以下对代码库的操作均需在本地工作目录中进行,即需要改变工作目录到本地代码库所在的目录中。 ls -al total 0 drwxr-xr-x 3 root root 26 Oct 21 01:45 . drwxrwxr-x 3 1002 1002 26 Oct 21 01:45 .. drwxr-xr-x 7 root root 155 Oct 21 01:45 .git 接下来,创建一个文件readme.txt,内容为“hello, Git!”(使用shell的输出重定向符>,将echo "hello,Git!"命令的执行结果放入readme.txt文件中,详见教材,echo命令用于在命令行中显示一个字符串) echo "hello,Git!" > readme.txt ls -al total 16 drwxr-xr-x 3 root root 48 Oct 21 01:53 . drwxrwxr-x 3 1002 1002 26 Oct 21 01:45 .. drwxr-xr-x 7 root root 155 Oct 21 01:45 .git -rw-r--r-- 1 root root 11 Oct 21 01:53 readme.txt 查看一下工作目录的状态,使用git status命令。 git status On branch master Initial commit Untracked files: (use "git add <file>..." to include in what will be committed) readme.txt nothing added to commit but untracked files present (use "git add" to track) 输出结果显示,readme.txt处于未跟踪状态(untracked files),也就是说该文件未存入代码库中,仅保存在工作目录中。 要将readme.txt提交到远程代码库,需要进行三步操作(见图6): git add 用于添加文件到索引中,命令行形式为: git add 文件.... 参数"文件…"表示增加的文件可以是多个,用空格分割文件名。 git commit 用于提交到本地代码库中,命令行形式为: git commit -m <日志信息> [文件....] "-m <日志信息>"选项用于为这次提交设置一段描述文字(日志),便于根据日志区别多次提交记录。可以通过git log查看提交日志。 git push 用于将本地代码库提交远程代码库中(远程代码库需要事先建立,方法见后面的7.0的例子,这里的例子中没有提交到远程代码库),命令行形式为: git push [选项] [远程代码库名称] [分支名称] “远程代码库名称”是远程代码库具体地址的一个名称,以方便使用,可以通过"git remote -v"命令查看已有的远程代码库名称和对应的地址。这里我们没有远程代码库,所以该命令没有任何输出。 分支名称是使用git branch命令创建的,用于使用分支进行开发。默认情况下存在一个名称为master的分支(后面的描述中称作主干)。 继续示例的步骤,使用git add命令将文件加入暂存区: git add readme.txt 再次查看状态: git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: readme.txt 结果显示,readme.txt的状态可以被提交(changes to be committed),表示该文件已存放到暂存中,下一步,需要使用git commit将文件提交到本地代码库。 git commit -m "add readme" *** Please tell me who you are. Run git config --global user.email "you@example.com" git config --global user.name "Your Name" to set your account's default identity. Omit --global to set the identity only in this repository. fatal: unable to auto-detect email address (got 'root@c9aec9f34a47.(none)') 命令执行错误,提示需要设置用户姓名和邮件地址(用户姓名和邮件地址用于标志提交者,后述git log命令查看的提交日志中需要包括该信息),按照提示运行下面两条命令: git config --global user.email "test@example.com" git config --global user.name "5120140000" git commit -m "add readme" [master (root-commit) 8770539] add readme 1 file changed, 1 insertion(+) create mode 100644 readme.txt 显示执行成功,提交了1个文件。 现在,在readme.txt文件上做一些变化。(下面的命令用于往readme.txt文件中新增加一行,使用shell附加重定向符>>) echo 'hello, again\!' >> readme.txt 查看变化: git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a") 结果表明:readme.txt文件发生了变化,但是还没有放入暂存区(Changes not staged),如果需要保存到代码库,可以使用git add命令,然后使用git commit命令;如果需要覆盖本地工作目录中的内容(回滚),使用git checkout命令。 使用git diff命令查看一下本地工作目录和暂存区之间的文件变化: git diff readme.txt diff --git a/readme.txt b/readme.txt index a76b829..3f50efc 100644 --- a/readme.txt +++ b/readme.txt @@ -1 +1,2 @@ hello,Git! +hello, again! 结果的“@@ -1 +1,2 @@”中:前面的-1分成两个部分:减号表示第一个文件(即暂存区a中的readme.txt文件),"1"表示第1行,合在一起,就表示下面是第一个文件从第1行开始的连续3行;同样的,”+1,2"分成三个部分:加号表示第二个文件(即工作区b中的readme.txt),“1”表示第1行,"2"表示连续两行,合在一起,表示变动后,成为第二个文件从第1行开始的连续2行。之后是变动的过程。 接下来,把修改放入暂存: git add readme.txt 再次修改文件: echo "hello, the 3rd time!" >> readme.txt 查看文件变化: git diff diff --git a/readme.txt b/readme.txt index 3f50efc..47c5bc5 100644 --- a/readme.txt +++ b/readme.txt @@ -1,2 +1,3 @@ hello,Git! hello, again! +hello, the 3rd time! 结果可见:相比暂存中的文件(增加“hello,again!”行后提交的文件),增加了“hello, the 3rd time!”一行。 如果跟本地代码库中相比呢? git diff HEAD diff --git a/readme.txt b/readme.txt index a76b829..47c5bc5 100644 --- a/readme.txt +++ b/readme.txt @@ -1 +1,3 @@ hello,Git! +hello, again! +hello, the 3rd time! 结果可见:相比本地代码库中的文件(HEAD表示本地代码库的最新版本),本地工作目录中的文件增加了“hello, again!”和“hello, the 3rd time!”两行。 下面,将文件回滚/恢复到暂存区中的内容: git checkout -- readme.txt 命令中的"–"用于指明readme.txt是一个文件名称。 cat readme.txt hello,Git! hello, again! git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: readme.txt 结果可见:暂存区中的文件未提交到本地代码库。 暂存区中的文件回滚为本地代码库中的内容(暂存区中的文件被覆盖): git reset HEAD Unstaged changes after reset: M readme.txt 结果可见,暂存区不再为提交了的本地工作目录中的文件,即本地工作目录中的修改未暂存。 提交本地工作目录的变化到代码库。 git add readme.txt git commit -m "hello, again" [master 647dd6e] hello, again 1 file changed, 1 insertion(+) 结果表明,提交到本地代码库成功。 最后,如果需要将本地代码库提交到远程代码库中,则执行git push命令(这里暂时无法使用该命令,需要创建远程代码库(方法见7.1),并且使用git remote命令在本地工作目录中添加啊该远程代码库的地址,或者采用7.1的例子所示,通过git clone命令来保存远程代码库地址)。 注意:参考资料[2]是一个交互式git命令学习网站,对于学习git的基本概念和操作非常直观,建议访问。 7.2 Git命令行进行版本控制 这个例子采用Centralized Workflow,假想一个应用场景,介绍仅使用git命令行进行版本控制的具体方法: 假设有两个作家(分别为A和B)合作完成一篇文章README,这篇文章有两章:Chapter1和Chapter2,两个作家分别各自修改文章README,而修改进度无法保持一致,并且,两个作家需要能够访问到该文章的各个历史版本。 README文件最后的内容为: Chapter1 First Second Chapter2 First 为了模拟真实环境中的远程代码库,需要使用git init子命令生成(在真实项目中,一般由项目管理员在远程服务器上建立,下一个实验将介绍通过gitlab系统创建)。 cd /root/workspace/ git init --bare rep.git Initialized empty Git repository in /root/workspace/rep.git/ 上述命令创建了一个远程代码库,位于“/root/workspace/rep.git”目录中。 这里我们用存放在本地的代码库目录来模拟远程代码库,在实际使用场景中,远程代码库往往存放互联网中的服务器上,使用网络协议访问,但命令行是一致的。 ** 注意:区别于建立本地代码库,建立远程代码库需要使用–bare选项 ** ls -al rep.git total 40 drwxr-xr-x 7 root root 4096 Oct 8 06:44 . drwxrwxr-x 4 1000 1000 4096 Oct 8 06:44 .. -rw-r--r-- 1 root root 23 Oct 8 06:44 HEAD drwxr-xr-x 2 root root 4096 Oct 8 06:44 branches -rw-r--r-- 1 root root 66 Oct 8 06:44 config -rw-r--r-- 1 root root 73 Oct 8 06:44 description drwxr-xr-x 2 root root 4096 Oct 8 06:44 hooks drwxr-xr-x 2 root root 4096 Oct 8 06:44 info drwxr-xr-x 4 root root 4096 Oct 8 06:44 objects drwxr-xr-x 4 root root 4096 Oct 8 06:44 refs 可以看到,远程代码库目录中包含的文件并不是需要进行版本控制的文件本身(此时远程代码库中并没有需要控制的文档)。从这里也能看出远程代码库的目录结构和工作目录的区别,前面部分介绍的本地代码库的目录结构(查看/root/workspace/proj/.git目录结构)跟远程代码库的是相似的。 为了模拟作家A和B分别对文章README进行修改,生成A、B两个工作目录,假设在A目录中的操作用于模拟作家A在一台计算机上对代码库进行操作,同样的,在B目录中的操作用于模拟作家B在一台计算机上对代码库进行操作,两位作家间无法统一提交修改的顺序。 例子采用Centralized Workflow的版本控制流程,如图7所示: 作家合作修改README的流程(主干方式) 图7 作家合作修改README的流程(主干方式) 根据图7中的流程,两位作家利用git所进行的操作步骤主要包括: (1) 两位作家分别创建工作目录 作家A和B需要“拷贝”远程代码库以创建自己的本地工作拷贝,其中包括本地代码库、索引文件(即暂存文件)和工作目录,注意,本地代码库和索引文件位于工作目录中,是隐藏的(位于工作目录的.git子文件夹中),本地工作拷贝是工作目录中其他可见的内容,后文中,我们不加区别地称该目录为工作目录。 从远程代码库通过“拷贝方式”创建本地工作目录的命令为: git clone [选项] ADDRESS [本地工作目录] ADDRESS是远程代码库的路径,本地工作目录如果没有指明,则来自ADDRESS中指出的路径(去掉.git后缀)。 ADDRESS由”协议://主机/目录”构成,如果采用网络连接代码库,则协议部分为http或svn,如: “http://vlab.cs.swust.edu.cn:8081/linuxCourse/linuxer.git” ,如果采用本地模拟的远程代码库(如本例),则协议部分为file,如:“file:///root/workspace/rep.git” 。 git clone命令实际上根据远程代码库,创建了本地拷贝中的本地代码库、缓存目录以及本地工作目录(本地代码库和缓存目录都在本地工作目录中,且默认不可见,后面不加区别的称呼本地代码库、本地拷贝和本地工作目录),并保存了远程代码库地址的名称为origin,便于使用git push命令提交本地代码库到远程代码库时使用。 作家A创建工作目录,使用如下命令: git clone file:///root/workspace/rep.git A Cloning into 'A'... warning: You appear to have cloned an empty repository. Checking connectivity... done. ls -l total 168 drwxr-xr-x 3 root root 4096 Oct 8 06:47 A drwxr-xr-x 3 root root 4096 Oct 8 06:42 proj drwxr-xr-x 7 root root 4096 Oct 8 06:44 rep.git 可见,生成的工作目录为A,拷贝自远程代码库“file:///notebooks/workspace/rep.git” 作家B创建工作目录,使用如下命令: git clone file:///notebooks/workspace/rep.git B Cloning into 'B'... warning: You appear to have cloned an empty repository. Checking connectivity... done. ls -l total 168 drwxr-xr-x 3 root root 4096 Oct 8 06:47 A drwxr-xr-x 3 root root 4096 Oct 8 06:47 B drwxr-xr-x 3 root root 4096 Oct 8 06:42 proj drwxr-xr-x 7 root root 4096 Oct 8 06:44 rep.git (2) 作家A生成初始文章README 模拟作家A进行文章编写: cd A git status命令可以查看工作目录中的文件状态,可以是修改过的、新创建的、已经暂存但未提交的等。 git status On branch master Initial commit nothing to commit (create/copy files and use "git add" to track) 结果显示,目前没有东西需要提交。(nothing to commit) 模拟作家A新增加README文件,内容为: Chapter1 Chapter2 请使用文本编辑器命令vi生成上述文件(/notebooks/workspace/A/README)。查看下文件的内容: cat README Chapter1 Chapter2 请试试git status命令,查看下本地工作目录中的文件状态。 作家A增加README到本地代码库中,执行命令: git add README git commit -m "Initiate README" [master (root-commit) 2aead62] Initiate README 1 file changed, 2 insertions(+) create mode 100644 README 接下来,将本地代码库提交到远程代码库上: git push origin master Counting objects: 3, done. Writing objects: 100% (3/3), 227 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To file:///root/workspace/rep.git * [new branch] master -> master git push命令用于将本地代码库提交到origin所指示的远程代码库中,其命令行形式为: git push [远程代码库名称] [本地分支名]:[远程分支名] 上述命令的执行中,远程代码库的分支名称为master,远程代码库名称为origin(因为前面执行git clone命令时已经默认保存了远程代码库的地址,该地址的名称为origin),可以通过git remote命令查看所有远程代码库的名称和对应的地址。如: git remote -v origin file:///root/workspace/rep.git/ (fetch) origin file:///root/workspace/rep.git/ (push) 上述命令结果表明:远程代码库origin的地址是file:///root/workspace/rep.git/,其中拷贝/抓取(fetch)操作和提交(push)操作的远程代码库地址是一样的。 (3) 作家B更新README的版本1 模拟作家B进行操作。 cd ../B 从代码库上获取更新到工作目录,使用git pull操作,命令行形式为: git pull <远程主机名> <远程分支名>:<本地分支名> 如果省略本地分支名,则意味着与远程分支名相同。 作家B获取README文件的变化,使用git pull命令: git pull origin master remote: Counting objects: 3, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From file:///root/workspace/rep * branch master -> FETCH_HEAD * [new branch] master -> origin/master 结果显示从远程代码库file:///root/workspace/rep.git更新本地代码库成功。 ls -l total 4 -rw-r--r-- 1 root root 18 Oct 8 07:16 README cat README Chapter1 Chapter2 结果显示,工作目录文件更新成功。 (4) 作家A修改README文档,形成版本2 cd ../A 模拟作家A进行操作。修改REAME文件为: Chapter1 First Chapter2 请打开一个命令行窗口,使用文本编辑器命令vi修改上述文件(/root/workspace/A/README)。 cat README Chapter1 First Chapter2 git add README git commit -m "Add First in Chapter1 by A" [master 851e614] Add First in Chapter1 by A 1 file changed, 1 insertion(+) git push origin master Counting objects: 5, done. Writing objects: 100% (3/3), 266 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To file:///root/workspace/rep.git 2aead62..851e614 master -> master 将README的更新提交到远程中心代码库。 (5) 作家B得到远程中心代码库上的新版本,在其上进行修改 cd ../B 模拟作家B进行操作。 git pull origin master remote: Counting objects: 5, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From file:///notebooks/workspace/rep * branch master -> FETCH_HEAD 2aead62..851e614 master -> origin/master Updating 2aead62..851e614 Fast-forward README | 1 + 1 file changed, 1 insertion(+) 作家B进行更新操作("Fast-forward"表示工作目录中的README文件被更新了)。 cat README Chapter1 First Chapter2 作家B修改文章为: Chapter1 1st Chapter2 First 打开一个命令行窗口,使用文本编辑器命令vi修改上述文件(/root/workspace/B/README)。 cat README Chapter1 1st Chapter2 First (6) 与此同时,作家A修改文章 cd ../A 模拟作家A的操作。作家A修改README文件为: Chapter1 First Second Chapter2 cat README Chapter1 First Second Chapter2 git add README git commit -m "Add Chapter1 by A" [master 0c1c56e] Add Chapter1 by A 1 file changed, 1 insertion(+) git push origin master Counting objects: 5, done. Writing objects: 100% (3/3), 267 bytes | 0 bytes/s, done. Total 3 (delta 0), reused 0 (delta 0) To file:///root/workspace/rep.git 851e614..0c1c56e master -> master 作家A提交修改到远程中心代码库。 (7) 作家A提交后,作家B提交(5)中所做的修改 cd ../B git add README git commit -m "Modify Chapter1 by B" [master ce92fc0] Modify Chapter1 by B 1 file changed, 2 insertions(+), 1 deletion(-) git push origin master To file:///root/workspace/rep.git ! [rejected] master -> master (fetch first) error: failed to push some refs to 'file:///root/workspace/rep.git' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. 提示远程库有更新,需要先执行git pull操作,再执行git push操作。 接下来执行: git pull origin master remote: Counting objects: 5, done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From file:///root/workspace/rep * branch master -> FETCH_HEAD 851e614..0c1c56e master -> origin/master Auto-merging README CONFLICT (content): Merge conflict in README Automatic merge failed; fix conflicts and then commit the result. 提示README文件产生了冲突,查看README文件: cat README Chapter1 <<<<<<< HEAD 1st ======= First Second >>>>>>> 0c1c56ee3b8bc55136acc11faeeb5431b1890a4d Chapter2 First 文件中标志了冲突部分:<<<<<<< HEAD和=======之间为B的本地代码库中的内容,即B的修改;=======和>>>>>>>之间是远程代码库中的内容,即A已提交的修改。 打开一个命令行窗口,使用文本编辑器命令vi修改上述文件(/root/workspace/B/README)为: Chapter1 1st Chapter2 First cat README Chapter1 1st Chapter2 First 提交更新到远程代码库。 git status On branch master Your branch and 'origin/master' have diverged, and have 1 and 1 different commit each, respectively. (use "git pull" to merge the remote branch into yours) You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: README no changes added to commit (use "git add" and/or "git commit -a") 结果显示README文件冲突,提示使用git add和git commit命令提交。 git add README git commit -m "Merged by B" [master 840f888] Merged by B git push origin master Counting objects: 8, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (4/4), 386 bytes | 0 bytes/s, done. Total 4 (delta 1), reused 0 (delta 0) To file:///root/workspace/rep.git 0c1c56e..840f888 master -> master git status On branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working directory clean 结果显示冲突解决,提交成功。 (8) 作家A获取文章的更新 cd ../A 模拟作家A操作,执行更新操作: git pull origin master remote: Counting objects: 8, done. remote: Compressing objects: 100% (2/2), done. remote: Total 4 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (4/4), done. From file:///root/workspace/rep * branch master -> FETCH_HEAD 0c1c56e..840f888 master -> origin/master Updating 0c1c56e..840f888 Fast-forward README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 结果显示,README已更新(“1 file changed”)。查看该文档: cat README Chapter1 1st Chapter2 First 8 实验任务 请继续7.2的例子(如图7 作家合作修改README的流程(主干方式)),将README文件修改为: Chapter1 First Second Chapter2 First 其中,Chapter1的部分由A完成,Chapter2的部分由B完成。 9 实验思考 1、以7.2中的过程为例子,描述源代码版本控制中的“冲突”是如何产生的?如何解决?给我实验任务的完整代码,不要只给我实验任务的,完全从0开始
最新发布
11-27
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值