lang_process() (一)

news2025/1/21 7:12:33

一、lang_process() 

        从现在开始介绍 lang_process()函数,是GNU ld(GNU链接器)的一个核心函数,负责执行链接过程中的各个关键操作。lang_process(void) 函数涵盖了整个链接过程中的各个关键步骤,包括符号解析、重定位、大小计算、内存区域管理、节的映射和输出文件的生成等这个函数的功能非常复杂,它确保了链接器能够将多个目标文件合并成最终的可执行文件或共享库。

void
lang_process (void)
{
  /* Finalize dynamic list.  */
  if (link_info.dynamic_list)
    lang_finalize_version_expr_head (&link_info.dynamic_list->head);

  current_target = default_target;

  /* Open the output file.  */
  lang_for_each_statement (ldlang_open_output);  // 输出一下
  init_opb ();  // x = 1

  ldemul_create_output_section_statements ();  // 机器相关,x86函数为NULL

  /* Add to the hash table all undefineds on the command line.  */
  lang_place_undefineds ();  // 将未定义符号加入link_info.hash

  if (!bfd_section_already_linked_table_init ())  // 初始化了一个hash table
    einfo (_("%P%F: Failed to create hash table\n"));

  /* Create a bfd for each input file.  */
  current_target = default_target;  // elf64-x86-64
  open_input_bfds (statement_list.head, OPEN_BFD_NORMAL);  // 王子龙在看

#ifdef ENABLE_PLUGINS
  if (link_info.lto_plugin_active)  // 没进
    {
      lang_statement_list_type added;
      lang_statement_list_type files, inputfiles;

      /* Now all files are read, let the plugin(s) decide if there
	 are any more to be added to the link before we call the
	 emulation's after_open hook.  We create a private list of
	 input statements for this purpose, which we will eventually
	 insert into the global statement list after the first claimed
	 file.  */
      added = *stat_ptr;
      /* We need to manipulate all three chains in synchrony.  */
      files = file_chain;
      inputfiles = input_file_chain;
      if (plugin_call_all_symbols_read ())
	einfo (_("%P%F: %s: plugin reported error after all symbols read\n"),
	       plugin_error_plugin ());
      /* Open any newly added files, updating the file chains.  */
      open_input_bfds (*added.tail, OPEN_BFD_NORMAL);
      /* Restore the global list pointer now they have all been added.  */
      lang_list_remove_tail (stat_ptr, &added);
      /* And detach the fresh ends of the file lists.  */
      lang_list_remove_tail (&file_chain, &files);
      lang_list_remove_tail (&input_file_chain, &inputfiles);
      /* Were any new files added?  */
      if (added.head != NULL)
	{
	  /* If so, we will insert them into the statement list immediately
	     after the first input file that was claimed by the plugin.  */
	  plugin_insert = find_replacements_insert_point ();
	  /* If a plugin adds input files without having claimed any, we
	     don't really have a good idea where to place them.  Just putting
	     them at the start or end of the list is liable to leave them
	     outside the crtbegin...crtend range.  */
	  ASSERT (plugin_insert != NULL);
	  /* Splice the new statement list into the old one.  */
	  lang_list_insert_after (stat_ptr, &added,
				  &plugin_insert->header.next);
	  /* Likewise for the file chains.  */
	  lang_list_insert_after (&input_file_chain, &inputfiles,
				  &plugin_insert->next_real_file);
	  /* We must be careful when relinking file_chain; we may need to
	     insert the new files at the head of the list if the insert
	     point chosen is the dummy first input file.  */
	  if (plugin_insert->filename)
	    lang_list_insert_after (&file_chain, &files, &plugin_insert->next);
	  else
	    lang_list_insert_after (&file_chain, &files, &file_chain.head);

	  /* Rescan archives in case new undefined symbols have appeared.  */
	  open_input_bfds (statement_list.head, OPEN_BFD_RESCAN);
	}
    }
#endif /* ENABLE_PLUGINS */

  /* Make sure that nobody has tried to add a symbol to this list
     before now.  */
  ASSERT (link_info.gc_sym_list == NULL);

  link_info.gc_sym_list = &entry_symbol;  // {next = 0x55555599c470, name = 0x555555988b10 "_start"}

  if (entry_symbol.name == NULL)
    {
      link_info.gc_sym_list = ldlang_undef_chain_list_head;

      /* entry_symbol is normally initialied by a ENTRY definition in the
	 linker script or the -e command line option.  But if neither of
	 these have been used, the target specific backend may still have
	 provided an entry symbol via a call to lang_default_entry().
	 Unfortunately this value will not be processed until lang_end()
	 is called, long after this function has finished.  So detect this
	 case here and add the target's entry symbol to the list of starting
	 points for garbage collection resolution.  */
      lang_add_gc_name (entry_symbol_default);
    }

  lang_add_gc_name (link_info.init_function);
  lang_add_gc_name (link_info.fini_function);

  ldemul_after_open ();
  if (config.map_file != NULL)
    lang_print_asneeded ();

  bfd_section_already_linked_table_free ();  // 为啥又把table free了???

  /* Make sure that we're not mixing architectures.  We call this
     after all the input files have been opened, but before we do any
     other processing, so that any operations merge_private_bfd_data
     does on the output file will be known during the rest of the
     link.  */
  lang_check ();

  /* Handle .exports instead of a version script if we're told to do so.  */
  if (command_line.version_exports_section)
    lang_do_version_exports_section ();

  /* Build all sets based on the information gathered from the input
     files.  */
  ldctor_build_sets ();  // 符号相关的

  /* PR 13683: We must rerun the assignments prior to running garbage
     collection in order to make sure that all symbol aliases are resolved.  
这段注释的意思是,为了确保所有符号别名(symbol aliases)都得到解析,
我们必须在运行垃圾回收(garbage collection)之前重新运行赋值(assignments)过程。
在这里,“符号别名”指的是在链接脚本或源代码中通过别名来引用的符号。
例如,你可以在链接脚本或源代码中将一个符号(Symbol)用另一个名称来引用,
这被称为符号别名。这种情况下,为了确保所有的别名都指向了正确的符号,
需要在运行垃圾回收之前重新进行一次符号赋值的过程。
这是一个确保程序在连接时符号引用正确的重要步骤。 */
  lang_do_assignments (lang_mark_phase_enum);  // 看看

  lang_do_memory_regions();
  expld.phase = lang_first_phase_enum;

  /* Size up the common data.  */
  lang_common ();  // 处理符号的

  /* Remove unreferenced sections if asked to.  */
  lang_gc_sections ();  // 删除不用的节,ld --gc-sections开启

  /* Check relocations.  */
  lang_check_relocs ();

  /* Update wild statements.排序用的  */
  update_wild_statements (statement_list.head);

  /* Run through the contours of the script and attach input sections
     to the correct output sections.  */
  lang_statement_iteration++;
  map_input_to_output_sections (statement_list.head, NULL, NULL);  // 重点看看!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  process_insert_statements ();

  /* Find any sections not attached explicitly and handle them.  */
  lang_place_orphans ();

  if (!bfd_link_relocatable (&link_info))
    {// 进来了
      asection *found;

      /* Merge SEC_MERGE sections.  This has to be done after GC of
	 sections, so that GCed sections are not merged, but before
	 assigning dynamic symbols, since removing whole input sections
	 is hard then.  */
      bfd_merge_sections (link_info.output_bfd, &link_info);

      /* Look for a text section and set the readonly attribute in it.  */
      found = bfd_get_section_by_name (link_info.output_bfd, ".text");

      if (found != NULL)
	{
	  if (config.text_read_only)// 进了
	    found->flags |= SEC_READONLY;
	  else
	    found->flags &= ~SEC_READONLY;
	}
    }

  /* Do anything special before sizing sections.  This is where ELF
     and other back-ends size dynamic sections.  */
  ldemul_before_allocation ();

  /* We must record the program headers before we try to fix the
     section positions, since they will affect SIZEOF_HEADERS.  */
  lang_record_phdrs ();

  /* Check relro sections.  */
  if (link_info.relro && !bfd_link_relocatable (&link_info))  // 进了
    lang_find_relro_sections ();  // 这在干啥????

  /* Size up the sections.  */
  lang_size_sections (NULL, !RELAXATION_ENABLED);  // 看看

  /* See if anything special should be done now we know how big
     everything is.  This is where relaxation is done.  */
  ldemul_after_allocation ();

  /* Fix any .startof. or .sizeof. symbols.  */
  lang_set_startof ();  // 符号相关的

  /* Do all the assignments, now that we know the final resting places
     of all the symbols.  */
  lang_do_assignments (lang_final_phase_enum);

  ldemul_finish ();  // 删除Execule中的符号,改为使用keep中的符号

  /* Convert absolute symbols to section relative.  */
  ldexp_finalize_syms ();  // 处理链接脚本定义的绝对符号

  /* Make sure that the section addresses make sense.  */
  if (command_line.check_section_addresses)  // 进了
    lang_check_section_addresses ();

  /* Check any required symbols are known.  */
  ldlang_check_require_defined_symbols ();

  lang_end ();
}

二、lang_process执行函数

1.lang_finalize_version_expr_head

  if (link_info.dynamic_list)
    lang_finalize_version_expr_head (&link_info.dynamic_list->head);

1)link_info 是一个结构体或对象,用于存储与链接过程相关的各种信息和参数。它通常在链接器的代码中用来跟踪链接过程中的状态和配置。link_info 可能包含各种链接器的配置选项、输入文件列表、输出文件信息、符号表、段表、动态库信息等等,这取决于链接器的实现和配置。2)2)link_info.dynamic_listlink_info 结构体中的一个成员,用于存储与动态链接库版本信息和表达式相关的数据。

3)&link_info.dynamic_list->head:这是对动态链接库版本列表头部的引用。

4)lang_finalize_version_expr_head:这是链接器的一个函数或方法,它的目的是处理动态链接库版本信息和相关的表达式。这通常包括检查版本依赖关系,解析版本表达式,以及确保所链接的动态库版本符合版本规范。

2. lang_for_each_statement(打开输出文件)

        在lang_process()中调用lang_for_each_statement函数,并将 ldlang_open_output (这是一个函数)作为参数传入。函数 lang_for_each_statement 通常用于遍历链接器语句列表,并对每个语句执行特定的操作。在这种情况下,它将每个语句传递给 ldlang_open_output 函数。

lang_for_each_statement 函数的作用是遍历链接器的语句列表,对每个语句执行特定的操作。它通常用于链接器的初始化和处理过程中,以便处理链接脚本中定义的各种语句,执行必要的操作来构建可执行文件或共享库。

具体来说,lang_for_each_statement 的主要作用包括:

  1. 遍历链接器语句:它帮助链接器迭代链接器语句列表,这些语句包括符号定义、节定义、输入文件、输出文件、地址重定位等等。

  2. 执行用户自定义操作:用户可以通过传递一个函数指针给 lang_for_each_statement,指定要在每个链接器语句上执行的操作。这允许用户定义处理链接器语句的自定义逻辑,以适应特定需求。

  3. 确保正确的链接过程:链接器的处理过程通常需要根据链接脚本和命令行选项执行一系列操作,如为符号分配地址、将输入文件与输出文件相关联、应用重定位等。lang_for_each_statement 确保了这些操作能够正确执行。

  4. 实现链接脚本逻辑:在链接脚本中,通常会定义一系列的链接器语句,包括节的位置和属性、符号的地址分配等。lang_for_each_statement 用于实现这些链接脚本的逻辑。

        总之,lang_for_each_statement 是链接器内部用于管理和处理链接器语句的关键工具,它允许用户扩展和自定义链接过程,以适应不同的链接需求和目标。这种灵活性是链接器的一个关键特征,使其能够支持各种不同的输出文件格式和链接需求。

lang_for_each_statement (ldlang_open_output);

 lang_for_each_statement实现:

void
lang_for_each_statement (void (*func) (lang_statement_union_type *))
{
  lang_for_each_statement_worker (func, statement_list.head);
}

        lang_for_each_statement 实际调用的是lang_for_each_statement_worker函数,传递的参数为ldlang_open_output函数和statement_list结构体的head字段,即语句列表的首个语句。

  1. 它会调用 lang_for_each_statement_worker 函数,将传递给 lang_for_each_statementfunc 函数指针和语句链表 statement_list 的头部(statement_list.head)作为参数传递给 lang_for_each_statement_worker 函数。

  2. lang_for_each_statement_worker 函数会遍历 statement_list 中的每个语句,对每个语句都调用传入的 func 函数来执行特定的操作

static lang_statement_list_type statement_list;
typedef struct statement_list
{
  union lang_statement_union *  head;
  union lang_statement_union ** tail;
} lang_statement_list_type;
typedef union lang_statement_union
{
  lang_statement_header_type header;
  lang_wild_statement_type wild_statement;
  lang_data_statement_type data_statement;
  lang_reloc_statement_type reloc_statement;
  lang_address_statement_type address_statement;
  lang_output_section_statement_type output_section_statement;
  lang_assignment_statement_type assignment_statement;
  lang_input_statement_type input_statement;
  lang_target_statement_type target_statement;
  lang_output_statement_type output_statement;
  lang_input_section_type input_section;
  lang_common_statement_type common_statement;
  lang_object_symbols_statement_type object_symbols_statement;
  lang_fill_statement_type fill_statement;
  lang_padding_statement_type padding_statement;
  lang_group_statement_type group_statement;
  lang_insert_statement_type insert_statement;
} lang_statement_union_type;
typedef struct lang_statement_header_struct
{
  union lang_statement_union *next;
  enum statement_enum type;
} lang_statement_header_type;
enum statement_enum
{
  lang_output_section_statement_enum,0
  lang_assignment_statement_enum,1
  lang_input_statement_enum,2
  lang_address_statement_enum,3
  lang_wild_statement_enum,4
  lang_input_section_enum,5
  lang_object_symbols_statement_enum,6
  lang_fill_statement_enum,7
  lang_data_statement_enum,8
  lang_reloc_statement_enum,9
  lang_target_statement_enum,10
  lang_output_statement_enum,11
  lang_padding_statement_enum,12
  lang_group_statement_enum,13
  lang_insert_statement_enum,14
  lang_constructors_statement_enum
};

 

3.  ldlang_open_output

static void
ldlang_open_output (lang_statement_union_type *statement)
{
  switch (statement->header.type)
    {
    case lang_output_statement_enum:  // 只有是输出语句类型时
      ASSERT (link_info.output_bfd == NULL);
       // link_info.output_bfd 是一个 BFD 句柄,用于表示链接器正在生成的输出文件。
       // 它包含有关输出文件的各种信息,如文件格式、段、节、符号表等。通过该句柄,链接器可以访问和操作输出文件的各个部分,以正确地生成最终的目标文件或可执行文件。
      open_output (statement->output_statement.name);
      // 表示当前语句是描述输出文件的语句,该分支调用open_output函数打开该输出文件。statement->output_statement.name为输出文件的名称。
      ldemul_set_output_arch (); //   它将链接器的输出架构设置为与当前目标或配置相关的架构。
      
      if (config.magic_demand_paged
	  && !bfd_link_relocatable (&link_info))
	link_info.output_bfd->flags |= D_PAGED;
      else
	link_info.output_bfd->flags &= ~D_PAGED;
      if (config.text_read_only)
	link_info.output_bfd->flags |= WP_TEXT;
      else
	link_info.output_bfd->flags &= ~WP_TEXT;
      if (link_info.traditional_format)
	link_info.output_bfd->flags |= BFD_TRADITIONAL_FORMAT;
      else
	link_info.output_bfd->flags &= ~BFD_TRADITIONAL_FORMAT;
      break;

    case lang_target_statement_enum:
      current_target = statement->target_statement.target;
      // 表示当前语句是目标架构语句,该分支内部将当前目标架构设置为语句中指定的目标
      break;
    default:
      break;
    }
}

 4. lang_for_each_statement_worker

lang_for_each_statement_worker (void (*func) (lang_statement_union_type *),
				lang_statement_union_type *s)
 // 传进来的是ldlang_open_output函数以及statement_list.head
{
  for (; s != NULL; s = s->header.next)
    {
      func (s); // 调用ldlang_open_output

      switch (s->header.type)
	{
	case lang_constructors_statement_enum:
	  lang_for_each_statement_worker (func, constructor_list.head);
	  break;
	case lang_output_section_statement_enum:
	  if (s->output_section_statement.constraint != -1)
	    lang_for_each_statement_worker(func, 
                  s->output_section_statement.children.head);
	  break;
	case lang_wild_statement_enum:
	  lang_for_each_statement_worker (func,
					  s->wild_statement.children.head);
	  break;
	case lang_group_statement_enum:
	  lang_for_each_statement_worker (func,
					  s->group_statement.children.head);
	  break;
	case lang_data_statement_enum:
	case lang_reloc_statement_enum:
	case lang_object_symbols_statement_enum:
	case lang_output_statement_enum:
	case lang_target_statement_enum:
	case lang_input_section_enum:
	case lang_input_statement_enum:
	case lang_assignment_statement_enum:
	case lang_padding_statement_enum:
	case lang_address_statement_enum:
	case lang_fill_statement_enum:
	case lang_insert_statement_enum:
	  break;
	default:
	  FAIL ();
	  break;
	}
    }
}

5.open_output

static void
open_output (const char *name)
{
  // 确定输出文件的目标架构(用户指出/其它/默认) elf64-x86-64
  output_target = lang_get_output_target ();

  // 查看生成的目标文件与用户指定的字节序一致,选择适当的输出目标(output_target)
  // 根据用户指定或者默认的目标架构来决定输出文件的字节序
  /* Has the user requested a particular endianness on the command
     line?  */
  // command_line.endian  用户的命令行
  if (command_line.endian != ENDIAN_UNSET)
    {
      // ENDIAN_UNSET 用于表示字节序未明确定义或未设置

      /* Get the chosen target.  */
      const bfd_target *target
	= bfd_iterate_over_targets (get_target, (void *) output_target);
  // target 用于存储当前遍历到的目标文件描述符。当找到与 output_target 匹配的目标时,target 将存储该匹配的目标。
  //目标文件格式:描述目标文件所采用的二进制文件格式,如ELF、COFF、Mach-O等。这包括文件头的结构和元数据。
  //目标体系结构:描述目标文件的目标计算机体系结构,如x86、ARM、MIPS等。这决定了目标文件的指令集和寄存器布局。
  //字节序(endianness):描述目标文件中数据的排列顺序,如大端序(Big-Endian)或小端序(Little-Endian)。
  //文件扩展名:用于标识目标文件格式的常见文件扩展名,如".elf"、".o"等。
  //其他格式特定的信息:包括段、节、符号表等目标文件的特定信息。
      /* If the target is not supported, we cannot do anything.  */
      if (target != NULL)
	{
	  enum bfd_endian desired_endian;

	  if (command_line.endian == ENDIAN_BIG)
	    desired_endian = BFD_ENDIAN_BIG;
	  else
	    desired_endian = BFD_ENDIAN_LITTLE;

	  /* See if the target has the wrong endianness.  This should
	     not happen if the linker script has provided big and
	     little endian alternatives, but some scrips don't do
	     this.  */
	  if (target->byteorder != desired_endian)
      // target->byteorder 表示目标文件描述符所使用的字节序
	    {
	      /* If it does, then see if the target provides
		 an alternative with the correct endianness.  */
	      if (target->alternative_target != NULL
		  && (target->alternative_target->byteorder == desired_endian))
		output_target = target->alternative_target->name;
	      else
		{
		  /* Try to find a target as similar as possible to
		     the default target, but which has the desired
		     endian characteristic.  */
		  bfd_iterate_over_targets (closest_target_match,
					    (void *) target);

		  /* Oh dear - we could not find any targets that
		     satisfy our requirements.  */
		  if (winner == NULL)
		    einfo (_("%P: warning: could not find any targets"
			     " that match endianness requirement\n"));
		  else
		    output_target = winner->name;
		}
	    }
	}
    }

  link_info.output_bfd = bfd_openw (name, output_target);
  // 该函数接受输出文件的名称和目标架构作为参数
  // 链接器需要将生成的代码和数据写入此输出文件。
  // 调用bfd_openw函数打开输出文件,并赋值给link_info.output_bfd,设置link_info;

  if (link_info.output_bfd == NULL)
    {
      if (bfd_get_error () == bfd_error_invalid_target)
	einfo (_("%P%F: target %s not found\n"), output_target);

      einfo (_("%P%F: cannot open output file %s: %E\n"), name);
    }

  delete_output_file_on_failure = TRUE;
  //  设置输出文件的格式
  if (!bfd_set_format (link_info.output_bfd, bfd_object))
    einfo (_("%P%F:%s: can not make object file: %E\n"), name);

  // 设置输出文件的架构
  if (!bfd_set_arch_mach (link_info.output_bfd,
			   ldfile_output_architecture,
			   ldfile_output_machine))
    einfo (_("%P%F:%s: can not set architecture: %E\n"), name);
  // 函数创建链接时使用的hash表,用于存储链接器符号表中的符号信息,用于符号解析和链接过程中的符号查找
  link_info.hash = bfd_link_hash_table_create (link_info.output_bfd);
  if (link_info.hash == NULL)
    einfo (_("%P%F: can not create hash table: %E\n"));

  bfd_set_gp_size (link_info.output_bfd, g_switch_value);
}

 6. bfd_openw

bfd *
bfd_openw (const char *filename, const char *target)
{
  bfd *nbfd;
  const bfd_target *target_vec;

  /* nbfd has to point to head of malloc'ed block so that bfd_close may
     reclaim it correctly.  */
  nbfd = _bfd_new_bfd ();
  if (nbfd == NULL)
    return NULL;

  target_vec = bfd_find_target (target, nbfd);
  if (target_vec == NULL)
    {
      _bfd_delete_bfd (nbfd);
      return NULL;
    }

  /* PR 11983: Do not cache the original filename, but
     rather make a copy - the original might go away.  */
  nbfd->filename = xstrdup (filename);
  nbfd->direction = write_direction;

  if (bfd_open_file (nbfd) == NULL)
    {
      /* File not writeable, etc.  */
      bfd_set_error (bfd_error_system_call);
      _bfd_delete_bfd (nbfd);
      return NULL;
  }

  return nbfd;
}

        在lang_process()中,调用lang_for_each_statement() ,将ldlang_open_output作为参数传入,实际调用的是lang_for_each_statement_worker函数,传递的参数为dlang_open_output函数与statement_list结构体的head字段,即语句列表的首个语句。lang_for_each_statement_worker函数最开始会先运行作为参数被传递进来的ldlang_open_output函数,并把另一个参数statement_list结构体的head字段传递给ldlang_open_output函数。

open_output函数主要工作为:

1.确定输出文件的目标架构;

2.根据用户指定或者默认的目标架构来决定输出文件的字节序;

3.调用bfd_openw函数打开输出文件,并赋值给link_info.output_bfd,设置link_info;

4.调用bfd_link_hash_table_create函数创建链接时使用的hash表,用于存储链接器符号表中的符号信息,用于符号解析和链接过程中的符号查找。

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

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

相关文章

Spring Boot Actuator 漏洞利用

文章目录 前言敏感信息泄露env 泄露配置信息trace 泄露用户请求信息mappings 泄露路由信息heapdump泄露堆栈信息 前言 spring对应两个版本,分别是Spring Boot 2.x和Spring Boot 1.x,因此后面漏洞利用的payload也会有所不同 敏感信息泄露 env 泄露配置信…

【使用Python编写游戏辅助工具】第二篇:键盘监听的应用

前言 这里是【使用Python编写游戏辅助工具】的第二篇:键盘监听的应用。本文主要介绍使用Python实现事件监听功能。 键盘监听是指通过编程的方式监控用户在键盘上的按键操作。 在这里键盘监听的主要用途是: 监听我们按下的按键,如果按下了指…

@Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成

问题 Tag和Operation标签失效 但是Schema标签有效 pom依赖 <!-- 接口文档--><!--引入openapi支持--><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><vers…

最长回文子串-LeetCode5 动态规划

由于基础还不是很牢固 一时间只能想到暴力的解法: 取遍每个子串 总数量nn-1n-2…1 O(n^2) 判断每个子串是否属于回文串 O(n) 故总时间复杂度为O(n^3) class Solution { public:string longestPalindrome(string s) { int max0;string ret;for(int i0;i<s.size();i)for(int…

基于SSM+Vue的宠物用品电子商务平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

金山文档轻维表之删除所有行记录

目前脚本文档里面的只有删除行记录功能&#xff0c;但是需要指定ID值&#xff0c;不能实现批量删除&#xff0c;很多人反馈但是官方无回应&#xff0c;挺奇怪的 但是批量删除的需求我很需要&#xff0c;最后研究了一下&#xff0c;还是挺容易实现的 测试&#xff1a; 附上脚本…

年终总结一定用得上!这8款AI制作PPT软件不容错过。

PPT&#xff08;PowerPoint&#xff09;已成为日常商务办公、教育和营销环境中广泛使用的一种呈现工具。年终总结时&#xff0c;使用PPT能清晰、直观地展示一年的工作成果&#xff0c;从而让团队成员或上级领导更好地了解并评估工作表现。 在过去&#xff0c;创建精美和引人入…

[PHP]帮管客CRM客户管理系统 v5.1.0

帮管客CRM客户管理系统基于先进的CRM营销理念设计&#xff0c;集客户档案、销售记录、业务往来于一身&#xff0c;以凝聚客户关系、提升资源价值为核心&#xff0c;将潜在客户变为现实客户、从而提升销售量、提高用户的满意度&#xff0c;并增加企业竞争力。帮管客CRM是适用于中…

Redis4 渐进式遍历/自定义客户端/持久化

1.渐进式遍历 1.keys *一次性把所有的key都获取到.但是存在一个问题,一旦数据过多,redis就会被阻塞住,就无暇顾及其他的命令,这样的影响很大. 2.那么就出现了渐进式遍历,可以做到既能获取所有的key,又不会阻塞服务器.渐进式不是一个命令把所有的key获取到,而是没执行一次命令只…

MySQL的3种索引合并优化⭐️or到底能不能用索引?

MySQL的3种索引合并优化⭐️or到底能不能用索引? 前言 前文我们讨论过MySQL优化回表的多种方式&#xff1a;索引条件下推ICP、多范围读取MRR、覆盖索引等 这篇文章我们来聊聊MySQL提供的另一种优化回表的手段&#xff1a;index merge 索引合并 在阅读本文前&#xff0c;你…

windows + Mingw32-make 编译 PoDoFo库,openssl, libjpeg, Msys2工具的使用

参考&#xff1a; https://blog.csdn.net/sspdfn/article/details/104244306 https://blog.csdn.net/yaoyuanyylyy/article/details/17436303 https://blog.csdn.net/wxlfreewind/article/details/106492253 前期进行了各种摸索&#xff0c;由于Podofo依赖库比较多&#xff0c…

Android系统Launcher启动流程学习(一)init启动部分

init进程学习&#xff1a; 文件路径system/core/init/init.cpp 解析init.rc配置文件&#xff0c;首先开启ServiceManager和MediaServer等关键进程init进程fork启动Zygote服务进程处理子进程的终止(signal方式)提供属性服务的功能 int main(int argc, char** argv) {//注释一…

Problem I. Magic Potion--2018ICPC南京

解析&#xff1a; 对于英雄跑一边二分图匹配&#xff0c;记录res1 再跑一边二分图匹配&#xff0c;记录res2 答案即为res1min&#xff08;k&#xff0c;res2&#xff09; #include<bits/stdc.h> using namespace std; int n,m,k; int g[510][510],match[510],st[510]; b…

【Python全栈_公开课学习记录】

一、初识python (一).Python起源 Python创始人为吉多范罗苏姆&#xff08;荷兰&#xff09;&#xff0c;Python崇尚优美、清晰、简明的编辑风格。Python语言结构清晰简单、数据库丰富、运行成熟稳定&#xff0c;科学计算统计分析领先。目前广泛应用于云计算、Web开发、科学运算…

BlockingQueue解析

BlockingQueue其实就是阻塞队列&#xff0c;是基于阻塞机制实现的线程安全的队列。 BlockingQueue不同于普通的Queue的区别主要是&#xff1a; 通过在入队和出队时进行加锁&#xff0c;保证了队列线程安全支持阻塞的入队和出队方法&#xff1a;当队列满时&#xff0c;会阻塞入…

数据结构与算法【01】—绪论

专栏地址:数据结构与算法专栏 开源仓库:bigsai-algorithm仓库 ,欢迎支持 针对以前写的数据结构与算法系列重写(针对文字描述、图片、错误修复),改动会比较大,一直到更新完为止 前言 数据结构与算法是程序员内功体现的重要标准之一,且数据结构也应用在各个方面,业界更有…

3.18每日一题(奇偶性、奇偶性的平移、几何意义、配方、换元)

解法一&#xff1a;先配方&#xff0c;再用三角函数换元&#xff08;看见根号一般用三角函数&#xff09;&#xff0c;看见对称区间联想奇偶性&#xff0c;最后再用公式 解法二&#xff1a; 利用奇偶性的平移&#xff0c;令&#xff08;x-1&#xff09; t &#xff0c;对应的区…

ROS学习笔记(4):ROS架构和通讯机制

前提 前4篇文章以及帮助大家快速入门ROS了&#xff0c;而从第5篇开始我们会更加注重知识积累。同时我强烈建议配合B站大学的视频一起服用。 1.ROS架构三层次&#xff1a; 1.基于Linux系统的OS层&#xff1b; 2.实现ROS核心通信机制以及众多机器人开发库的中间层&#xff1b…

[H5动画制作系列]坐标转化问题一次搞清,一了百了

前言: 本次演示的坐标包括三个坐标层&#xff1a; 1.舞台上的某位置相对于舞台的全局坐标的坐标(黑色)。 2.舞台上蓝色实例内部某位置相对于该蓝色实例内部局部坐标的坐标(蓝色)。 3.舞台上蓝色实例内部的红色实例内部某位置相对该红色实例内部局部坐标的坐标(红色)。 舞台…

Linux Makefile变量详解

前言 我们是地球人。曾经为复杂的 Makefile 变量而苦恼过吗&#xff1f;这就是我们的用武之地。我们简化您的构建流程&#xff0c;以获得更快、更高效的结果。看看我们。 自 1976 年出现以来&#xff0c;Make 一直在帮助开发人员自动执行编译代码、构建可执行文件和生成文档的…