关于编程生成的目标文件到底是怎么链接生成可执行文件的

news2024/12/18 21:07:22

以c/c++程序为例,要想生成可执行文件,需要经过如下步骤:

预处理之后的代码
汇编文件
目标文件
启动代码以及库文件
预处理
编译
汇编
链接
可执行文件
test.c
test.i
test.s
test.o
test.exe

那么每一个目标文件到底是如何组合在一起,生成可执行文件的?在这里插入图片描述

在linux中,对于目标文件和可执行代码,都是elf文件,这种文件表示了可执行代码应该如何被排放在内存中。

ELF文件

在这里插入图片描述从上述图中,可以看到对于ELF文件,其可以划分为4个部分:
ELF文件头,程序头表,节,节头表

  • ELF文件头:包含了ELF文件的一些信息,如:使用的编译平台(x86, AMD等),32bit或者64bit等
  • 程序头表:只在可执行文件中存在,指示哪些节应该放在虚拟内存中的哪个位置
  • 节:真实存在的程序数据内容
  • 节表:标识每个节存在的位置

上面四个部分分别对应<elf.h>中的四个数据结构

  • Elf64_Ehdr
  • Elf64_Phdr
  • Elf64_Shdr
  • Elf64_Sym

示例分析程序是如何加载的

先定义一个我们需要定义的函数在obj.c

int add5(int num) {
    return num + 5;
}
int add10(int num) {
    return num + 10;
}

将其编译为目标文件

gcc -c obj.c

下面我们从另一个文件loader.c中对该函数进行调用:
首先定义一个load_obj函数,将目标文件的内容加载到内存中

static void load_obj(void)
{
    struct stat sb;

    int fd = open("obj.o", O_RDONLY);
    if (fd <= 0) {
        perror("Cannot open obj.o");
        exit(errno);
    }

    /* we need obj.o size for mmap(2) */
    if (fstat(fd, &sb)) {
        perror("Failed to get obj.o info");
        exit(errno);
    }

    /* mmap obj.o into memory */
    obj.base = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (obj.base == MAP_FAILED) {
        perror("Maping obj.o failed");
        exit(errno);
    }
    close(fd);
}

其中重点需要关注的为mmap函数,其负责将目标文件的内容加载到内存中
在这里插入图片描述
参数addr代表内存的起始地址,如果为NULL的话,有内核自动选择。
参数Length代表想要从文件读入内存的数据的多少
参数prot代表读入的内存的权限,可执行,可读,可写,不可访问
返回开始的内存地址
综上:如果load_obj函数成功执行,那么就会将目标文件读取到内存地址addr

然后在定义一个解析函数parse_obj

static void parse_obj(void)
{
    /* the sections table offset is encoded in the ELF header */
    sections = (const Elf64_Shdr *)(obj.base + obj.hdr->e_shoff);
    /* the index of `.shstrtab` in the sections table is encoded in the ELF header
     * so we can find it without actually using a name lookup
     */
    shstrtab = (const char *)(obj.base + sections[obj.hdr->e_shstrndx].sh_offset);

    /* find the `.symtab` entry in the sections table */
    const Elf64_Shdr *symtab_hdr = lookup_section(".symtab");
    if (!symtab_hdr) {
        fputs("Failed to find .symtab\n", stderr);
        exit(ENOEXEC);
    }

    /* the symbols table */
    symbols = (const Elf64_Sym *)(obj.base + symtab_hdr->sh_offset);
    /* number of entries in the symbols table = table size / entry size */
    num_symbols = symtab_hdr->sh_size / symtab_hdr->sh_entsize;

    const Elf64_Shdr *strtab_hdr = lookup_section(".strtab");
    if (!strtab_hdr) {
        fputs("Failed to find .strtab\n", stderr);
        exit(ENOEXEC);
    }

    strtab = (const char *)(obj.base + strtab_hdr->sh_offset);

    /* get system page size */
    page_size = sysconf(_SC_PAGESIZE);

    /* find the `.text` entry in the sections table */
    const Elf64_Shdr *text_hdr = lookup_section(".text");
    if (!text_hdr) {
        fputs("Failed to find .text\n", stderr);
        exit(ENOEXEC);
    }

    /* allocate memory for `.text` copy rounding it up to whole pages */
    text_runtime_base = mmap(NULL, page_align(text_hdr->sh_size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (text_runtime_base == MAP_FAILED) {
        perror("Failed to allocate memory for .text");
        exit(errno);
    }

    /* copy the contents of `.text` section from the ELF file */
    memcpy(text_runtime_base, obj.base + text_hdr->sh_offset, text_hdr->sh_size);

    /* make the `.text` copy readonly and executable */
    if (mprotect(text_runtime_base, page_align(text_hdr->sh_size), PROT_READ | PROT_EXEC)) {
        perror("Failed to make .text executable");
        exit(errno);
    }
}

其中obj是一个结构体,其定义如下:

typedef union {
    const Elf64_Ehdr *hdr;
    const uint8_t *base;
} objhdr;

load_obj函数中,obj已经指向了目标文件开始的位置
关于Elf64_Ehdr结构体的定义:

在这里插入图片描述

Elf64_Half --> 2Byte
Elf64_Word --> 4Byte
Elf64_Addr --> 4Byte
Elf64_Off  ->> 4Byte

其中EI_NINDENT为一个常数:16
我们调试程序,得到其执行完load_obj函数之后,obj结构体对应的结果:
在这里插入图片描述
我们利用hexdump工具,查看目标函数obj.o中的数据
在这里插入图片描述
Elf64_Ehdr结构体中的e_ident代表了一组魔数,一共16个字节,可以看到和obj.o文件中的数据相同
e_type两个字节了,代表了Elf文件的类型:
在这里插入图片描述
从上面调试的结果可以看到,obj.o文件为Relocatable file
e_machine两个字节,代表编译文件的系统架构
e_version四个字节,代表了文件使用Elf的版本
在这里插入图片描述
e_entry四个字节,代表了入口的虚拟内存地址
e_phoff四个字节,代表了程序表头相对于入口地址的偏移量
e_shoff四个字节,代表了节表头相对于入口地址的偏移量
e_epsize两个字节,代表了Elf文件头的大小
e_phentsize两个字节,代表了程序头表中一个数据的大小
e_phnum两个字节,代表了程序头表中数据的个数
e_shentsize两个字节,代表了节头表中一个数据的大小
e_shnum两个字节,代表了节头表中数据的个数
e_shstrndex两个字节,代表字符串表在节头表中的下标

下面对parse_obj函数中的每一行代码进行分析:

sections = (const Elf64_Shdr *)(obj.base + obj.hdr->e_shoff);

找到节表的在内存中的地址

shstrtab = (const char *)(obj.base + sections[obj.hdr->e_shstrndx].sh_offset);

这里有一个新的数据结构Elf64_Shdr
在这里插入图片描述
首先利用obj.hdr->e_shstrndx找到内存中节表中字符串表在节表中的位置,然后利用sh_offset找到字符串表在内存中的位置,既最终shstrtab指向字符串表在内存中的地址。

const Elf64_Shdr *symtab_hdr = lookup_section(".symtab");
    if (!symtab_hdr) {
        fputs("Failed to find .symtab\n", stderr);
        exit(ENOEXEC);
    }

上述代码找打.symtab在节表中的数据,先假设lookup_seciton可以正确地找到,并返回指定节的信息,后面在对其及进行分析。

symbols = (const Elf64_Sym *)(obj.base + symtab_hdr->sh_offset);

读取符号节表的信息

num_symbols = symtab_hdr->sh_size / symtab_hdr->sh_entsize;

得到符号节表中有多少个元数据

const Elf64_Shdr *strtab_hdr = lookup_section(".strtab");
    if (!strtab_hdr) {
        fputs("Failed to find .strtab\n", stderr);
        exit(ENOEXEC);
    }

strtab = (const char *)(obj.base + strtab_hdr->sh_offset);

找到.strtab节,然后将strtab指向.strtab的内存地址

page_size = sysconf(_SC_PAGESIZE);

得到系统页大小

const Elf64_Shdr *text_hdr = lookup_section(".text");
	if (!text_hdr) {
		fputs("Failed to find .text\n", stderr);
		exit(ENOEXEC);
}

找到.text节的信息

	text_runtime_base = mmap(NULL, page_align(text_hdr->sh_size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (text_runtime_base == MAP_FAILED) {
        perror("Failed to allocate memory for .text");
        exit(errno);
    }

需要注意,这里在fd参数的位置传入-1,将会导致只分配指定大小page_align(text_hdr->sh_size)大小的内存空间,而不填入数据。

memcpy(text_runtime_base, obj.base + text_hdr->sh_offset, text_hdr->sh_size);

.text节中的数据写入到分配的内存空间中

    if (mprotect(text_runtime_base, page_align(text_hdr->sh_size), PROT_READ | PROT_EXEC)) {
        perror("Failed to make .text executable");
        exit(errno);
    }

将拷贝到内存中.text的数据变成只读和可执行

下面分析关于lookup_section函数

static const Elf64_Shdr *lookup_section(const char *name)
{
    size_t name_len = strlen(name);

    /* number of entries in the sections table is encoded in the ELF header */
    for (Elf64_Half i = 0; i < obj.hdr->e_shnum; i++) {
        /* sections table entry does not contain the string name of the section
         * instead, the `sh_name` parameter is an offset in the `.shstrtab`
         * section, which points to a string name
         */
        const char *section_name = shstrtab + sections[i].sh_name;
        size_t section_name_len = strlen(section_name);

        if (name_len == section_name_len && !strcmp(name, section_name)) {
            /* we ignore sections with 0 size */
            if (sections[i].sh_size)
                return sections + i;
        }
    }

    return NULL;
}

就是遍历节表中所有元数据,找到和执行节表名称相同的元数据返回

下面需要调用obj.o中的函数

static void execute_funcs(void)
{
    /* pointers to imported add5 and add10 functions */
    int (*add5)(int);
    int (*add10)(int);

    add5 = lookup_function("add5");
    if (!add5) {
        fputs("Failed to find add5 function\n", stderr);
        exit(ENOENT);
    }

    puts("Executing add5...");
    printf("add5(%d) = %d\n", 42, add5(42));

    add10 = lookup_function("add10");
    if (!add10) {
        fputs("Failed to find add10 function\n", stderr);
        exit(ENOENT);
    }

    puts("Executing add10...");
    printf("add10(%d) = %d\n", 42, add10(42));
}

大致流程就是利用函数指针,然后通过lookup_function找到函数,并将其地址进行返回,最后通过函数指针来调用函数。
下面为lookup_function函数:

static void *lookup_function(const char *name)
{
    size_t name_len = strlen(name);

    /* loop through all the symbols in the symbol table */
    for (int i = 0; i < num_symbols; i++) {
        /* consider only function symbols */
        if (ELF64_ST_TYPE(symbols[i].st_info) == STT_FUNC) {
            /* symbol table entry does not contain the string name of the symbol
             * instead, the `st_name` parameter is an offset in the `.strtab`
             * section, which points to a string name
             */
            const char *function_name = strtab + symbols[i].st_name;
            size_t function_name_len = strlen(function_name);

            if (name_len == function_name_len && !strcmp(name, function_name)) {
                /* st_value is an offset in bytes of the function from the
                 * beginning of the `.text` section
                 */
                return text_runtime_base + symbols[i].st_value;
            }
        }
    }

    return NULL;
}

函数,全局变量,静态变量的信息都存放在symbol table中,下面是Elf64_Sym的数据结构:
在这里插入图片描述
参数st_name表示,该符号量对应的名称在字符串表中的下标
参数st_info表示,该符号量是函数,还是全局变量,静态变量等
参数st_shndx表示,该符号量在哪一个节中
参数st_value表示,该符号量的值,函数地址,值(取决于info)

由于我们需要找到的函数的信息:

  • 找到函数类型的符号量
  • 检查函数名是否相同
  • 找到符合符号量之后,返回其对应的地址

完整的程序代码为:

/* compile and link:
 * $ gcc -o loader loader.c
 */

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

/* for open(2), fstat(2) */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

/* for close(2), fstat(2) */
#include <unistd.h>

/* for mmap(2) */
#include <sys/mman.h>

/* parsing ELF files */
#include <elf.h>

/* for errno */
#include <errno.h>

typedef union {
    const Elf64_Ehdr *hdr;
    const uint8_t *base;
} objhdr;

/* obj.o memory address */
static objhdr obj;

/* sections table */
static const Elf64_Shdr *sections;
static const char *shstrtab = NULL;

/* symbols table */
static const Elf64_Sym *symbols;
/* number of entries in the symbols table */
static int num_symbols;
static const char *strtab = NULL;

static uint64_t page_size;

/* runtime base address of the imported code */
static uint8_t *text_runtime_base;

static inline uint64_t page_align(uint64_t n)
{
    return (n + (page_size - 1)) & ~(page_size - 1);
}

static void load_obj(void)
{
    struct stat sb;

    int fd = open("obj.o", O_RDONLY);
    if (fd <= 0) {
        perror("Cannot open obj.o");
        exit(errno);
    }

    /* we need obj.o size for mmap(2) */
    if (fstat(fd, &sb)) {
        perror("Failed to get obj.o info");
        exit(errno);
    }

    /* mmap obj.o into memory */
    obj.base = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (obj.base == MAP_FAILED) {
        perror("Maping obj.o failed");
        exit(errno);
    }
    close(fd);
}

static const Elf64_Shdr *lookup_section(const char *name)
{
    size_t name_len = strlen(name);

    /* number of entries in the sections table is encoded in the ELF header */
    for (Elf64_Half i = 0; i < obj.hdr->e_shnum; i++) {
        /* sections table entry does not contain the string name of the section
         * instead, the `sh_name` parameter is an offset in the `.shstrtab`
         * section, which points to a string name
         */
        const char *section_name = shstrtab + sections[i].sh_name;
        size_t section_name_len = strlen(section_name);

        if (name_len == section_name_len && !strcmp(name, section_name)) {
            /* we ignore sections with 0 size */
            if (sections[i].sh_size)
                return sections + i;
        }
    }

    return NULL;
}

static void *lookup_function(const char *name)
{
    size_t name_len = strlen(name);

    /* loop through all the symbols in the symbol table */
    for (int i = 0; i < num_symbols; i++) {
        /* consider only function symbols */
        if (ELF64_ST_TYPE(symbols[i].st_info) == STT_FUNC) {
            /* symbol table entry does not contain the string name of the symbol
             * instead, the `st_name` parameter is an offset in the `.strtab`
             * section, which points to a string name
             */
            const char *function_name = strtab + symbols[i].st_name;
            size_t function_name_len = strlen(function_name);

            if (name_len == function_name_len && !strcmp(name, function_name)) {
                /* st_value is an offset in bytes of the function from the
                 * beginning of the `.text` section
                 */
                return text_runtime_base + symbols[i].st_value;
            }
        }
    }

    return NULL;
}

static void parse_obj(void)
{
    /* the sections table offset is encoded in the ELF header */
    sections = (const Elf64_Shdr *)(obj.base + obj.hdr->e_shoff);
    /* the index of `.shstrtab` in the sections table is encoded in the ELF header
     * so we can find it without actually using a name lookup
     */
    shstrtab = (const char *)(obj.base + sections[obj.hdr->e_shstrndx].sh_offset);

    /* find the `.symtab` entry in the sections table */
    const Elf64_Shdr *symtab_hdr = lookup_section(".symtab");
    if (!symtab_hdr) {
        fputs("Failed to find .symtab\n", stderr);
        exit(ENOEXEC);
    }

    /* the symbols table */
    symbols = (const Elf64_Sym *)(obj.base + symtab_hdr->sh_offset);
    /* number of entries in the symbols table = table size / entry size */
    num_symbols = symtab_hdr->sh_size / symtab_hdr->sh_entsize;

    const Elf64_Shdr *strtab_hdr = lookup_section(".strtab");
    if (!strtab_hdr) {
        fputs("Failed to find .strtab\n", stderr);
        exit(ENOEXEC);
    }

    strtab = (const char *)(obj.base + strtab_hdr->sh_offset);

    /* get system page size */
    page_size = sysconf(_SC_PAGESIZE);

    /* find the `.text` entry in the sections table */
    const Elf64_Shdr *text_hdr = lookup_section(".text");
    if (!text_hdr) {
        fputs("Failed to find .text\n", stderr);
        exit(ENOEXEC);
    }

    /* allocate memory for `.text` copy rounding it up to whole pages */
    text_runtime_base = mmap(NULL, page_align(text_hdr->sh_size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (text_runtime_base == MAP_FAILED) {
        perror("Failed to allocate memory for .text");
        exit(errno);
    }

    /* copy the contents of `.text` section from the ELF file */
    memcpy(text_runtime_base, obj.base + text_hdr->sh_offset, text_hdr->sh_size);

    /* make the `.text` copy readonly and executable */
    if (mprotect(text_runtime_base, page_align(text_hdr->sh_size), PROT_READ | PROT_EXEC)) {
        perror("Failed to make .text executable");
        exit(errno);
    }
}

static void execute_funcs(void)
{
    /* pointers to imported add5 and add10 functions */
    int (*add5)(int);
    int (*add10)(int);

    add5 = lookup_function("add5");
    if (!add5) {
        fputs("Failed to find add5 function\n", stderr);
        exit(ENOENT);
    }

    puts("Executing add5...");
    printf("add5(%d) = %d\n", 42, add5(42));

    add10 = lookup_function("add10");
    if (!add10) {
        fputs("Failed to find add10 function\n", stderr);
        exit(ENOENT);
    }

    puts("Executing add10...");
    printf("add10(%d) = %d\n", 42, add10(42));
}

int main(void)
{
    load_obj();
    parse_obj();
    execute_funcs();

    return 0;
}

运行结果:

在这里插入图片描述

文章使用的代码来源:地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1121920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SLAM从入门到精通(rviz自定义形状)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 rviz作为很好的上位机调试工具&#xff0c;它本身可以显示很多的传感器数据。比如说lidar、map、tf、camera、点云这些&#xff0c;在rviz上面显示…

Stable Diffusion新玩法火了!给几个词就能生成动图,连动图人物的表情和动作都能随意控制

夕小瑶科技说 原创 作者 | 小戏 单说大模型 AI 的发展对人们想象力释放的助力&#xff0c;基于 Stable Diffusion 模型的方法首当其冲。透过文本描述到图像生成技术&#xff0c;大模型为我们的想象力打开了一个恢弘的梦幻世界。透过点滴文字&#xff0c;就有可能重现禁锢在我们…

【Java 进阶篇】深入浅出:Bootstrap 轮播图

在现代网页设计中&#xff0c;轮播图是一个常见的元素。它们可以用于展示图片、广告、新闻、产品或任何您希望吸引用户注意力的内容。要实现一个轮播图&#xff0c;您通常需要一些复杂的HTML、CSS和JavaScript代码&#xff0c;这对于初学者来说可能会感到困难。但幸运的是&…

HWUI源码剖析(二) - 终于讲清楚OpenGL渲染的MVP矩阵的来龙去脉

概述 研究android8.1 HWUI源码的过程中,发现OpenGL是绕不过的一个知识点,不理解OpenGL的绘制基础,必然无法很好的理解Hwui基本原理,同时熟悉OpenGL之后,HWUI也是一个非常优秀的OpenGL 2D渲染的代码,本文将介绍一下OpenGL绘制图形的重要原理,为学习HWUI源码扫清障碍,本…

04、Python 爬取免费小说思路

目录 Python 爬取免费小说思路代码解析爬取东西基本的四行代码:user-agent安装模块从 bs4 导入 BeautifulSoup ,查询某个标签开头的数据筛选遍历获取小说的章节名称每章小说的链接获取请求网址的响应获取小说的内容筛选内容整理内容爬取下载到指定文件夹完整代码:Python 爬取…

HTML+CSS+JS+Django 实现前后端分离的科学计算器、利率计算器(附全部代码在gitcode链接)

&#x1f9ee;前后端分离计算器 &#x1f4da;git仓库链接和代码规范链接&#x1f4bc;PSP表格&#x1f387;成品展示&#x1f3c6;&#x1f3c6;科学计算器&#xff1a;1. 默认界面与页面切换2. 四则运算、取余、括号3. 清零Clear 回退Back4. 错误提示 Error5. 读取历史记录Hi…

2023年【化工自动化控制仪表】最新解析及化工自动化控制仪表作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 化工自动化控制仪表最新解析参考答案及化工自动化控制仪表考试试题解析是安全生产模拟考试一点通题库老师及化工自动化控制仪表操作证已考过的学员汇总&#xff0c;相对有效帮助化工自动化控制仪表作业考试题库学员顺…

Linux进程(四)--进程地址空间(一)

前言&#xff1a;在Linux中&#xff0c;每个正在运行的进程都有自己独立的虚拟地址空间&#xff0c;该虚拟地址空间是逻辑上的抽象&#xff0c;用于在进程间提供隔离和保护。它将进程的内存分配和访问从物理内存中分离出来&#xff0c;为每个进程提供了一个独立的地址空间。这究…

【试题036】赋值运算符小例题2

1.题目&#xff1a;设int a0&#xff0c;b0&#xff0c;m0&#xff0c;n0;,执行(mab)/(nab)后m和n的值分别&#xff1f; 2.代码分析&#xff1a; int main() {//设int a0&#xff0c;b0&#xff0c;m0&#xff0c;n0;,执行(mab)/(nab)后m和n的值分别int a 0,b 0,m 0,n 0,…

openCV的CUDA GPU 版本安装 (Ubuntu windows 通用)

需要做template match, 比较注重时间&#xff0c;因此opencv 的普通版本不适用。需要用GPU 的。 下载 git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git确保准备好以下内容 1&#xff1a; visual studio &#xff0…

吉利银河L6顶配续航测试 记录 方便后续对比

智能模式 不设置保电&#xff08;优先用电&#xff09;强动能回收&#xff0c;7成道路开启了L2辅助驾驶。基本无急加速&#xff0c;急减速。 空调开了不到20min 驾驶总时长&#xff1a;3h 5min,平均车速&#xff1a;35Km/h 充电&#xff1a; 慢充到100% 胎压&#xff1a; 2…

使用树莓派(香橙派)搭建文件共享服务器-samba服务器

域网内部通过文件共享来传输文件是一种非常方便的方式&#xff0c;小米摄像头也支持用文件共享smb模式将视频备份到局域网中的文件服务器上。之前我一直使用荣耀pro路由器游戏版&#xff0c;是自带USB接口支持文件共享服务的&#xff0c;接上USB移动硬盘&#xff0c;小米摄像头…

推荐收藏系列!2万字图解Hadoop

今天我用图解的方式讲解pandas的用法&#xff0c;内容较长建议收藏&#xff0c;梳理不易&#xff0c;点赞支持。 学习 Python 编程&#xff0c;给我的经验就是&#xff1a;技术要学会分享、交流&#xff0c;不建议闭门造车。一个人可能走的很快、但一堆人可以走的更远。如果你…

Spring AOP 详细深入讲解+代码示例

Spring AOP 这里是引用 一&#xff0c;介绍 spring aop工作原理图 1.什么是spring aop Spring AOP&#xff08;Aspect-Oriented Programming&#xff09;是Spring框架提供的一种面向切面编程的技术。它通过将横切关注点&#xff08;例如日志记录、事务管理、安全性检查等&a…

solidworks 2024新功能之-让您的工作更加高效

您可以创建杰出的设计&#xff0c;并将这些杰出的设计将融入产品体验中。为了帮您简化和加快由概念到成品的产品开发流程&#xff0c;SOLIDWORKS 2024 涵盖全新的用户驱动型增强功能&#xff0c;致力于帮您实现更智能、更快速地与您的团队和外部合作伙伴协同工作。 SOLIDWORKS…

【JavaEE初阶】 线程池详解与实现

文章目录 &#x1f334;线程池的概念&#x1f384;标准库中的线程池&#x1f340;ThreadPoolExecutor 类&#x1f6a9;corePoolSize与maximumPoolSize&#x1f6a9;keepAliveTime&#x1f6a9;ThreadFactory&#x1f6a9;workQueue&#x1f6a9;RejectedExecutionHandler handl…

思科披露新的IOS XE零日漏洞,用于部署恶意软件植入

导语&#xff1a;思科最近披露了一个新的高危零日漏洞&#xff08;CVE-2023-20273&#xff09;&#xff0c;该漏洞被积极利用来在已经通过本周早些时候披露的CVE-2023-20198零日漏洞遭到侵害的IOS XE设备上部署恶意植入物。 漏洞披露 思科最近披露了一款名为CVE-2023-20273的高…

Leetcode-Easy题解1-回文数字

目录 解法1解法2 解法1 自己的想法,直接转成字符串首尾俩下标同时遍历比较 class Solution {public boolean isPalindrome(int x) {if(x<0){return false;}String strString.valueOf(x);int i0;for (;i<str.length()>>1;i){if(str.charAt(i)!str.charAt(str.leng…

Unity中Shader阴影的接收

文章目录 前言一、阴影接受的步骤1、在v2f中添加UNITY_SHADOW_COORDS(idx),unity会自动声明一个叫_ShadowCoord的float4变量&#xff0c;用作阴影的采样坐标.2、在顶点着色器中添加TRANSFER_SHADOW(o)&#xff0c;用于将上面定义的_ShadowCoord纹理采样坐标变换到相应的屏幕空间…

DC-4 靶机

DC_4 信息搜集 存活检测 详细扫描 后台网页扫描 网页信息搜集 只有一个登陆界面 漏洞利用 尝试使用 burpsuite 密码爆破 尝试使用用户名 admin 登录管理员页面 成功爆破出密码 happy 登录管理员页面 显示可以使用命令 但只能使用三个命令 继续使用 bp 拦截查看数据包…