推荐工具：开源免费的OCR工具-视频下载工具
AI摘要 本文介绍了两款开源免费工具：Text Grab和ytDownloader。Text Grab是一款OCR工具，能够识别屏幕上任何可见文字并直接复制到剪贴板，基于Windows内置的OCR API实现，支持全屏截取、框选识别、文本编辑和快速查找等多种工作模式。ytDownloader是一款视频下载工具，支持YouTube、Facebook、Instagram、TikTok、Twitter等主流平台，还内置了硬件加速和视频压缩功能，支持批量下载整个播放列表，以及提供字幕下载和时间范围等自定义选项。这两款工具均可在GitHub上找到。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); Text Grab 这款开源免费的 OCR 工具想要获取图片里的文字、或视频的字幕，有时候经常需要一个字一个字手动输入，颇为麻烦。最近发现 Text Grab 这款开源免费的 OCR 工具，能够识别屏幕上任何可见文字并直接复制到剪贴板。基于 Windows 内置的 OCR API 实现，无需联网，支持全屏截取、框选识别、文本编辑和快速查找等多种工作模式。GitHub：http://github.com/TheJoeFin/Text-Grab主要功能：全屏模式：框选任意区域或单击单词即可提取文字；抓取框模式：可调整大小的透明窗口，随时定位识别文字；文本编辑窗口：纯文本编辑器，支持大小写转换、去重、正则提取等；快速查找模式：存储常用文本片段，通过快捷键快速调用；批量处理：支持文件夹内所有图片的批量文字识别；命令行支持：可通过命令行参数启动不同模式。可从 Microsoft Store 或 GitHub 下载安装，也支持 scoop 和 choco 包管理器安装。功能强大的视频下载工具：ytDownloaderGitHub 上看到一个功能强大的视频下载工具：ytDownloader，完全开源免费，轻松下载各大平台的视频或音频。支持 YouTube、Facebook、Instagram、TikTok、Twitter 等主流平台，还内置了硬件加速和视频压缩功能。GitHub 地址：http://github.com/aandrew-me/ytdownloader除此之外，还可以批量下载整个播放列表，以及提供字幕下载和时间范围等自定义选项。提供了开箱即用安装包，支持 Windows、macOS 和 Linux 系统，有需要的同学可以下载对应的系统体验。
80+款AI工具，让数月工作在数分钟内完成
AI摘要 本文介绍了80多款AI工具，覆盖了研究、图像、文案、写作、网站、视频、会议、SEO、聊天机器人、演示、自动化、提示、UI/UX、设计、Logo生成器、音频、生产力和社交媒体管理等多个领域，旨在帮助用户提高工作效率，将数月的工作量缩短至几分钟内完成。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 80+ AI tools to finish months of work in minutes.80+款AI工具，让数月工作在数分钟内完成。ResearchChatGPTCopilotGeminiAbacusPerplexityImageFotorDalle 3Stability AIMidjourneyMicrosoft DesignerCopyWritingRytrCopy AIWritesonicAdcreative AIWritingJasperHIX AIJenny AITextblazeQuillbotWebsite10WebDurableFramerStyle AIVideoKlapOpusEightifyInVideoHeyGenRunwayImgCreator AIMorphstudio .xyzMeetingTldvOtterNoty AIFirefliesSEOVidIQSeona AIBlogSEOKeywrds aiChatbotDroxyChatbaseMutual infoChatsimplePresentationDecktopusSlides AIGamma AIDesigns AIBeautiful AIAutomationMakeZapierXemblyBardeenPromptsFlowGPTAlicent AIPromptBoxPromptbaseSnack PromptUI/UXFigmaUizardUiMagicPhotoshopDesignCanvaFlair AIDesignifyClipdropAutodrawMagician designLogo GeneratorLookaDesigns AIBrandmarkStockimg AINamecheapAudioLovo aiEleven labsSongburst AIAdobe PodcastProductivityMerlinTinywowNotion AIAdobe SenseiPersonal AISocial media managementTapiloTypefullyHypefuryTweetHunter
开源免费，平替微软Power BI的商业智能系统
AI摘要 Metabase是一个开源免费的商业智能工具，可以生成可视化数据报表，支持多种数据库，适合企业、团队和个人开发者使用。它的核心功能包括无需SQL即可提问、强大的SQL编辑器、交互性强的可视化仪表盘、调度与提醒、嵌入支持和多数据库支持。Metabase可以通过Docker快速部署，也可以在本地进行测试开发。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 你是否还在为团队分享数据报表、制作漂亮的图表和分析报告而感到头疼？不想用价格高昂的PowerBI ？今天要向你推荐一个超棒的开源工具——Metabase！它解决了零 SQL 基础小白也能快速生成可视化数据报表的大痛点，无论是团队协作、嵌入图表还是定制仪表盘，Metabase 都能帮你轻松搞定！而且，它完全开源！太绝了！Metabase 是什么Metabase 是一个功能强大的开源商业智能（BI）工具，大家可以用它轻松完成数据分析和展示工作。无论你懂不懂 SQL，只要拖拖拽拽，就能把你想要的业务数据整理成美观、交互丰富的仪表盘。它支持多种数据库，适合企业、团队甚至个人开发者使用。开源成就Star 数 GitHub 上已经斩获了 42.4k Star，是备受瞩目的优质开源项目！主开发语言 主要由 Clojure 和 TypeScript 开发，还有其他语言如 JavaScript、Shell 等，共同构建了这一强大的工具。核心功能无需 SQL 就能轻松提问不会写 SQL？没关系！Metabase 的拖拽式操作可以让任何人快速上手，无需技术背景也能完成数据查询。强大的 SQL 编辑器对于技术人员来说，它内置了一个 SQL 编辑器，方便写更复杂的自定义查询。漂亮且交互性强的可视化仪表盘可通过过滤、自动刷新、全屏显示等功能，创建具有高颜值的仪表盘。调度与提醒支持按设置的时间表，定期将数据结果发送到电子邮件或团队的 Slack。强大的嵌入支持你可以在自己的应用中嵌入 Metabase 图表、仪表盘，甚至整个 Metabase。 多数据库支持提供对 MySQL、PostgreSQL、MongoDB 等许多数据库的官方支持。国际化语言支持Metabase 支持多语言界面，目前正在不断扩展更多语言，让全球用户更便捷地使用。安装指南以下是快速部署 Metabase 的简要教程，简单几步就能运行：使用 Docker 部署：docker run -d -p 3000:3000 --name metabase metabase/metabase启动后访问：打开浏览器，进入 http://localhost:3000，按照页面提示配置数据库，就可以开始使用了！本地测试开发：仓库提供了开发环境启动方式，使用以下命令：git clone https://github.com/metabase/metabase.gitcd metabase./bin/build-drivers.shclojure -M:runMetabase 是一个让数据变得简单易懂的美妙工具。它不仅降低了数据分析的门槛，还为团队协作提供了强大的解决方案。无论是创业公司还是成熟企业，用 Metabase 可以快速构建出专业级的数据报表和仪表盘，数据分析能力立马拉满！开源地址https://github.com/metabase/metabase文章来源公众号：开源日志文章来源地址：https://mp.weixin.qq.com/s/H9SE8UjApciO2B5dffuc5Q
神器WindTerm斩获26.7K星，它专为开发者和运维工程师打造，可以极大地提升工作生产力
AI摘要 WindTerm是一款专为开发者和运维工程师设计的跨平台终端工具，支持SSH、Telnet、Serial等多种协议，并集成了SFTP文件管理、会话分屏等功能。它以其高性能和强大功能在GitHub上获得了26.7k星。WindTerm的主要特点包括多协议终端支持、高效的文件传输工具、会话管理功能、快速的性能表现、可视化与定制以及多平台兼容性。用户可以访问其GitHub页面下载并使用这款免费且高效的终端工具。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 作为一名开发者，你是否因为找不到高效的终端工具而头疼？WindTerm 是一款极致高效的终端工具，它的性能表现和功能设计几乎没有明显短板，尤其适合对速度和多功能性有高需求的开发者或运维工程师。如果你正在寻找一个能“一站式解决问题”的终端工具，WindTerm 绝对值得一试。此外，它完全免费，支持商业和个人无差别使用，简直就是开源软件爱好者的福利！今天给大家介绍一款神器——WindTerm，它专为开发者和运维工程师打造，可以极大地提升工作生产力！无论你是 SSH、SFTP 用户，还是经常需要使用 Telnet 或 Tmux，多合一的终端工具 WindTerm 都能帮你搞定！超高的性能和一系列强大功能，让无数用户直呼“真香”！一、WindTerm 是什么WindTerm 是一款专业且免费的跨平台终端工具。它不仅支持 SSH、Telnet、Serial、Shell 等多种协议，还集成了 SFTP 文件管理、会话分屏、自动完成等功能。相比传统终端工具，它运行更快、更流畅，是提升效率的最佳选择！ 二、开源成就Star 数 WindTerm 在 GitHub 上已经吸引了 26.7k 星。主开发语言 项目的代码主要使用 C 语言（占 85.6%） 编写，支持高效性能和稳定性。三、核心功能1、超强的多协议终端支持支持 SSH v2、Telnet、Serial、Raw TCP 以及 Shell 协议，甚至还能集成 Tmux 操作。2、高效的文件传输工具WindTerm 自带 SFTP 和 SCP 文件传输工具，不仅可以上传、下载文件，还可直接进行文件管理（如重命名、删除等）。3、强大的会话管理功能你可以在 WindTerm 中创建、重命名、恢复会话，同时支持自动日志记录和恢复上次会话布局。4、超快速的性能表现内存占用率极低，而文件传输速度实测可达 247 MB/s，下载速度吊打同类工具（FileZilla、WinSCP 等）。5、强大的可视化与多样化定制支持会话树、分屏操作、Vim 风格键位绑定，还能自由切换主题、设置窗口透明度并修改标签颜色。6、多平台兼容无论你用的是 Windows、MacOS 还是 Linux，WindTerm 都支持！还能搭配 bash、zsh、PowerShell 等进行使用。四、安装指南1、下载文件访问 WindTerm 的Release 页面下载适合你系统的二进制文件（支持 Windows、MacOS、Linux）。 2、解压并运行解压下载的压缩包后，直接运行主程序 WindTerm，无需安装。3、进行配置可以通过设置界面配置会话，设置连接协议（如 SSH、Telnet）。自定义界面主题、分屏布局后即可开始使用。开源地址：https://github.com/kingToolbox/WindTerm文章来源：https://mp.weixin.qq.com/s/Ls2v7PtxuxQizO7uuHEAMg
AI摘要 本文详细介绍了9大类型、50个实用的AI Agent开源工具，包括计算机与浏览器操作、框架、语音、文档理解、记忆、测试与评估、监控与可观测性、仿真环境和垂直领域Agent。这些工具可以帮助开发者构建和优化AI智能体，提高开发效率。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 嘿，大家好！这里是一个专注于前沿AI和智能体的频道~大模型发展迅速，Agent发展也很迅速。但是似乎，目前还很少有对开源工具栈进行整理的。一些综述提到的可能比较旧（不更新了），或者不实用，或者内容太多了。端午整理了一下，把压箱底的AI Agent开源工具栈全部整理出来~下面，我们按照构建Agent的逻辑顺序，逐一盘点。9大类型、50个实用工具合集！ 照旧，也做了一个网页展示（这次测试了一些比较酷的动效，需要网页源码的可以后台私信发送 Agent工具栈一、计算机与浏览器操作现在比较流行Compute Use，Agent必须能像人一样与电脑和网络交互——点击按钮、填写表单、抓取数据。这些工具就是连接“思考”与“行动”的桥梁。Open Interpreter[1]：将自然语言翻译成本地可执行代码。想移动文件或运行脚本？直接跟它说就行。 Self-Operating Computer[2]：赋予Agent完全控制你桌面环境的能力，让它像真人一样操作系统。 LaVague[3]：让Web Agent能够实时浏览网站、填写表单并做出决策，是浏览器任务自动化的理想选择。 Playwright[4]：微软官方自动化工具，用于跨浏览器操作，非常适合测试或模拟用户流程。 Puppeteer[5]：控制Chrome或Firefox的可靠工具，非常适合抓取数据和自动化前端行为。 [1] Open Interpreter: https://github.com/OpenInterpreter/open-interpreter[2] Self-Operating Computer: https://github.com/OthersideAI/self-operating-computer[3] LaVague: https://github.com/lavague-ai/LaVague[4] Playwright: https://github.com/microsoft/playwright[5] Puppeteer: https://pptr.dev/二、框架框架可以帮忙组织Agent的逻辑、连接大模型、管理工具，是整个系统的核心。CrewAI[6]：专为多智能体协作而生。当你的任务需要不同角色的Agent协同工作时，它就是最佳选择。 Phidata[7]：专注于记忆、工具使用和长期交互。非常适合构建需要记住上下文、能够持续进化的个人助理。 Camel[8]：为多智能体协作、模拟和任务专业化而设计。 AutoGPT[9]：通过规划和执行的循环来自动化复杂工作流。最适合需要独立运行的自主Agent。 AutoGen[10]：微软出品，让多个Agent能够相互沟通以解决复杂问题。 SuperAGI[11]：简化了构建和交付自主Agent的流程，主打一个快。 Superagent[12]：一个灵活的开源工具包，用于创建定制化的AI助理。 LangChain[13] & LlamaIndex](https://github.com/run-llama/llama_index)：这两个不用多说，已经是管理记忆、检索和工具链的事实标准。 [6] CrewAI: https://github.com/crewAIInc/crewAI[7] Phidata: https://github.com/agno-agi/agno[8] Camel: https://github.com/camel-ai/camel[9] AutoGPT: https://github.com/Significant-Gravitas/AutoGPT[10] AutoGen: https://github.com/microsoft/autogen[11] SuperAGI: https://github.com/TransformerOptimus/SuperAGI[12] Superagent: https://github.com/superagent-ai/superagent[13] LangChain: https://github.com/langchain-ai/langchain三、 语音语音是人机交互最自然的方式。这些工具负责处理语音识别、语音合成和实时交互，让你的Agent更具人性化。语音转语音 (Speech2Speech)Ultravox[14]：顶级的语音到语音模型，能流畅处理实时语音对话，响应速度极快。 Pipecat[15]：一个用于构建语音Agent的全栈框架，包含从语音到文本、文本到语音甚至视频交互的支持。 语音转文本 (Speech2Text)Whisper[16]：OpenAI的明星产品，多语言转录和语音识别的利器。 stable-ts[17]：一个对开发者更友好的Whisper封装，增加了时间戳和实时支持，非常适合对话型Agent。 Speaker Diarization 3.1[18]：Pyannote的模型，用于识别是谁在说话，是处理多人会议音频的关键。 文本转语音 (Text2Speech)ChatTTS[19]：目前我发现的最好的模型。速度快、稳定，对大多数用例来说都是生产就绪的。 ElevenLabs[20] (商业版)：当音质比开源更重要时，这是首选。声音高度自然。 Cartesia[21] (商业版)：另一个强大的商业选择，提供富有表现力的高保真语音合成。 [14] Ultravox: https://github.com/fixie-ai/ultravox[15] Pipecat: https://github.com/pipecat-ai/pipecat[16] Whisper: https://github.com/openai/whisper[17] stable-ts: https://github.com/jianfch/stable-ts[18] Speaker Diarization 3.1: https://huggingface.co/pyannote/speaker-diarization-3.1[19] ChatTTS: https://github.com/2noise/ChatTTS[20] ElevenLabs: https://elevenlabs.io/[21] Cartesia: https://cartesia.ai/四、 文档理解绝大多数有价值的数据都埋藏在PDF、扫描件这些非结构化文档里。这些工具能帮你的Agent直接阅读和理解这些“硬骨头”，无需复杂的OCR流程。Qwen2.5-VL[22]：来自阿里的强大视觉语言模型。在处理图文混合的复杂文档时，性能优于GPT-4和Claude 3.5 Sonnet。 DocOwl2[23]：一个轻量级的多模态模型，专为无OCR的文档理解而构建。速度快、效率高，能精准地从杂乱输入中提取结构和意义。 [22] Qwen2.5-VL: https://huggingface.co/collections/Qwen/qwen2-vl-66cee7455501d7126940800d[23] DocOwl2: https://huggingface.co/mPLUG/DocOwl2五、 记忆没有记忆的Agent，只能做一次性任务。想让它变得更聪明、更个性化，记忆是关键。Mem0[24]：一个自我优化的记忆层，让你的Agent能根据过去的互动进行调整，构建更持久和个性化的AI体验。 Letta (原MemGPT)[25]：为LLM Agent增加长期记忆和工具使用能力。可以看作是那些需要记忆、推理和进化的Agent的脚手架。 LangChain[26]：包含即插即用的记忆组件，用于跟踪对话历史和用户上下文，非常实用。 [24] Mem0: https://github.com/mem0ai/mem0[25] Letta (原MemGPT): https://github.com/letta-ai/letta[26] LangChain: https://github.com/langchain-ai/langchain六、 测试与评估Agent越复杂，就越容易在边缘情况下出bug。这些工具可以帮助你在不同场景下测试Agent的行为，尽早发现问题。AgentOps[27]：一套用于跟踪和基准测试AI Agent的工具，帮助你在问题影响用户之前发现并优化性能。 AgentBench[28]：一个评估LLM Agent在网页浏览、游戏等多种任务和环境中表现的基准测试工具。 eeVoice Lab[29]：一个用于测试语音Agent的综合框架，确保Agent的语音识别和响应准确自然。 [27] AgentOps: https://github.com/AgentOps-AI/agentops[28] AgentBench: https://github.com/THUDM/AgentBench[29] eeVoice Lab: https://github.com/saharmor/voice-lab七 监控与可观测性Agent上线后，性能、成本、稳定性如何？你需要“眼睛”来实时监控。openllmetry[30]：使用OpenTelemetry为LLM应用提供端到端的可观测性，让你清晰地了解Agent性能，并快速进行故障排除和优化。 AgentOps[31]：一个全面的监控工具，可以跟踪Agent的性能、成本和基准测试，帮助你确保Agent高效且在预算内运行。 [30] openllmetry: https://github.com/traceloop/openllmetry[31] AgentOps: https://github.com/AgentOps-AI/agentops八、 仿真环境在把Agent扔到真实世界前，最好先在沙盒里练练手。仿真环境让你可以在一个受控的世界里进行实验、完善决策逻辑，而没有真实世界风险。AgentVerse[32]：支持在不同应用和模拟中部署多个基于LLM的Agent。 AI Town[33]：一个虚拟小镇，AI角色在其中进行社交互动，用于测试决策能力和模拟真实世界场景。 Generative Agents[34]：斯坦福大学的项目，专注于创建模拟复杂人类行为的Agent，非常适合在社交环境中测试记忆和决策。 [32] AgentVerse: https://github.com/OpenBMB/AgentVerse[33] AI Town: https://github.com/a16z-infra/ai-town[34] Generative Agents: https://github.com/joonspk-research/generative_agents九、 垂直领域Agent不是所有轮子都要自己造。这些垂直领域的Agent开箱即用，或者可以作为你定制化开发的基础。编程：OpenHands[35]：一个由AI驱动的软件开发Agent平台，旨在自动化编码任务。 aider[36]：一个与终端直接集成的AI结对编程工具，你的命令行里的AI副驾。 GPT Engineer[37]：用自然语言构建应用；只需描述你想要什么，AI就会澄清需求并生成代码。 screenshot-to-code[38]：将截图转换为功能齐全的HTML/Tailwind/React/Vue网站，快速将设计稿变现。 [35] OpenHands: https://github.com/All-Hands-AI/OpenHands[36] aider: https://github.com/Aider-AI/aider[37] GPT Engineer: https://github.com/AntonOsika/gpt-engineer[38] screenshot-to-code: https://github.com/abi/screenshot-to-code研究：GPT Researcher[39]：一个自主研究Agent，能进行全面的资料搜集、数据分析并撰写报告。 [39] GPT Researcher: https://github.com/assafelovic/gpt-researcherSQL:Vanna[40]：用自然语言与你的SQL数据库交互；告别复杂的SQL命令，直接提问，Vanna就会帮你检索数据。 [40] Vanna: https://github.com/vanna-ai/vanna最后一个成功的Agent开发，关键不在于追逐每个热门新工具，而是务实地选择、组合、迭代。希望能给看到这里的小伙伴，提供一个高效率的起点，更快地构建出真正有价值的AI Agent。好了，这就是我今天想分享的内容。如果你对构建AI智能体感兴趣，别忘了点赞、关注噢~文章来源：公众号【探索AGI】https://mp.weixin.qq.com/s/1A7QZEVUQjEgWXb8QFxl-w
AI摘要 本文介绍了如何解决SQL Server 2008评估期已过的问题。首先，通过输入相应的产品密钥进行版本升级。如果升级后仍提示评估期已过，可以通过修改注册表中的“CommonFiles”值来解决。此外，文章还提供了解决SQL Server 2008安装需要一直重启的问题的方法，即删除注册表中的PendingFileRenameOperations键值。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 今天上班一打开SQL Server，就报了下面这个问题很显然，SQL Server 试用期到了，微软提醒我们购买。大家经济允许的话，还是要买正版。对于我们这些暂时无力承担正版费用的屌丝群体怎么办呢？显然，重装不是一个好的办法。SQL Server软件装起来很麻烦，还需要一段时间。况且光卸载老版本就是个问题，很难卸载干净。卸载加安装，半天就过去了。通过百度，找到了一些解决方案，通过自身的具体实践，终于把问题解决了，特将问题与解决方法记录下来，给遇到同样问题的伙伴提供一种解决方式，也方便自己日后查看。1.开始-->所有程序-->MicrosoftSQL Server 2008-->配置工具-->SQL Server 安装中心2.打开SQL Server安装中心(64位) 点击维护-->版本升级，如果提示失败1，电脑没有重启，这个时候就需要我们手动进行重启，重新打开电脑后，从步骤1开始；接着按照提示一直点下一步，到产品密钥的时候输入Microsoft SQL Server 2008 R2序列号密钥开发版32位：MC46H-JQR3C-2JRHY-XYRKY-QWPVM开发版64位：FTMGC-B2J97-PJ4QG-V84YB-MTXX8工组版：XQ4CB-VK9P3-4WYYH-4HQX3-K2R6QWEB版：FP4P7-YKG22-WGRVK-MKGMX-V9MTM数据中心版32位：PTTFM-X467G-P7RH2-3Q6CG-4DMYB数据中心版64位：DDT3B-8W62X-P9JD6-8MX7M-HWK38企业版32位：R88PF-GMCFT-KM2KR-4R7GB-43K4B企业版64位：GYF3T-H2V88-GRPPH-HWRJP-QRTYB标准版32位：CXTFT-74V4Y-9D48T-2DMFW-TX7CY标准版64位：B68Q6-KK2R7-89WGB-6Q9KR-QHFDW输入密钥之后进行升级，升级完成后，再次打开SQL Server。如果还是提示评估期已过，进行如下操作：修改注册表： win键 + r 打开运行界面 输入regedit 就能打开注册表页面找到 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MicrosoftSQL Server\100\ConfigurationState里的“CommonFiles”值改成3，在重新执行上面的操作。sql server 2008安装需要一直重启的解决办法：同样打开注册表找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager 位置然后在右边窗口右击PendingFileRenameOperations，选择删除，然后确认
AI摘要 马云在2020年的演讲中强调了2020年作为历史分水岭的重要性，并认为海南自贸港的建设是历史性的选择。他提出，新的全球化正在开始，将由发展中国家和中小企业主导，科技和数据流动将成为主力。马云还强调了中国内需对新一轮全球化的驱动作用，以及全球化是一种服务世界的能力。他鼓励企业家面向未来，用未来的方法解决今天的问题，并强调数字化是确定的趋势，将全面改造所有行业。马云还提到，企业家应有担当，为世界变得更好而努力。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 2020年非常特殊，未来一定是一段历史的分水岭；海南成为自贸港，也必定是一个历史性的选择；我希望我们CEC这次的海南会议，也能成为一个跨越时代的会议，在疫情之后，新的全球化开始之后，在整个数字时代到来的时候，我们CEC的企业家能够在这段历史中创造独特的价值。我昨天还在武汉，也和我们CEC的三十几位理事一起，到武汉去了一趟。几个月前在电视里看武汉，确实非常之难，这次到武汉，真的感到武汉是一座英雄的城市，有英雄气质。疫情是结束了，我们该考虑如何让经济重生。经济重生不是为了回到昨天、回到以前，而是在更高的水平上面，从疫情中看到未来，看到趋势，把握机会。这次从武汉来到海南，这里面我觉得是巧合，好像也有一种必然的东西。两年前，习主席宣布了建设海南自贸港，全面开放的战略，两年过去了，世界格局发生了巨大的变化。但是我依然相信，我们必须要坚持开放，必须要坚持全球化，特别是疫情之后，我相信全世界所有的门都是要靠自己去打开的，没有门是天然开着的，这时候坚持全球化比什么时候都重要、都有意义。现在海南就站在一个新的历史的起点上，自贸港建设不是简单的海南发展的机遇，更不仅仅是中国经济发展的一个机遇，而是要担当起为世界探索新的全球化的历史责任。我自己觉得，国际化和全球化是两个不同的概念，国际化更偏向于中国人跟外国人做生意，是双边为主。而全球化是世界各国的大事情，是一个全局的问题。今天很多人觉得全球化出现了这样那样的问题，而我并不这么认为：第一，我认为今天是真正全球化开始的时候。昨天的、原来的、传统的、工业时代的全球化正在终结。新的、真正的、数字时代的全球化才刚刚打开。以前，全球化是发达国家和大企业主导，未来，全球化应该是发展中国家、中小企业走向世界；以前贸易是全球化的主力，未来科技将是全球化的主力；以前是人在流动、货在流动，未来是数据在流动、服务在流动；以前是传统企业的全球化，未来是用好互联网技术的企业的全球化；未来，任何一个人，只要有一部手机就可以做全球生意，未来所有的中小企业都是跨国公司，过去三十年是6000家大企业决定了全球化，未来应该是6000万家中小企业决定全球化。地域的扩大就是业务的扩张，这是世界给予我们的巨大机会。第二， 这是由中国内需驱动的新一轮全球化。新一轮的全球化，中国将会从“卖卖卖”，变成“买买买”。海南自由贸易港有自己的历史使命，海南未来留在历史中将会成为一个非常重要的角色。假设我们对贸易游戏规则不满意，今天给了我们一次机会，我们是否有这种胸怀、有这种格局、有这种担当为世界未来数字的全球化，为更多企业、更多国家、更多发展中国家的全球化，来制定一个更加公平、可持续、绿色的贸易规则？大家其实不用害怕从“卖卖卖”变成“买买买”。中国每一次大门打开，都是中国进步的象征，每一次大门打开，中国都进步了。我们要不断地走出去。我记得我们以前的人走出去是找差距，我们觉得人家这个做得好、那个做得好。今天我们很多中国企业走出去，或者很多中国人走出去，是在找感觉。当我们在找感觉的时候，其实我们正在退步。上一次全球化，是美国3亿人消费驱动的；中国这次的全球化是14亿人的内需，会驱动下一轮真正的全球化，带动世界经济。进口不是终点，进口最终是要倒逼中国产业提升、消费升级，促进现代服务业的发展。对于企业家来说，未来的机会在中国那些百万人口的小城镇。我不知道大家是否知道中国有多少城市过100万人口，我在2014年初步做了一个统计，美国大概有不到12个城市过100万人口，中国有167个城市过100万人口。而100万人口的基础建设、100万人口所诞生的巨大产业，我们远远没有发展好。这些近百万城市的人口，也许我们将来会有三百个，这些地方会迸发出巨大的消费潜力。内需绝对不是有钱人的拉动，内需应该是满足每一个普通百姓的需求，普通人的内需拉动，才是真正可以持续发展的内需价值。第三，新的全球化是一种服务世界的能力。全球化的核心是在其他国家和地区创造价值，创造就业，去做当地做不到的事情。如果说过去中国走出去，必须要人走出去、机器走出去、资金走出去；今天的中国走出去需要信息走出去、服务走出去、价值走出去。全球化是一种服务世界的能力，不是赚世界钱的能力。中国企业应当坚定地走向全球，而不是去征服全球。很多人爬山，自己认为是去征服自然，但自然不是去征服的，自然应该是去臣服的，对于世界真正的价值，不是远征，而是去创造价值。我们走出去要赢回来的不仅仅是利润，更应该赢回来的是尊重；我们要展示的不是一个强大的国家，而是一个善良美好的国家；我们不想去转移过剩产能，而是要到当地创造新的、不同的价值，尊重当地的文化、尊重当地的价值观、尊重当地的宗教和信仰、尊重每个国家不同的机制和体制。我们要相信这个世界因为不同而美好。都像你一样，也不行，都像别人一样，也不行。2020年应该说是一个转折之年，我和很多企业家探讨，发现大家或多或少都有点焦虑。其实谁都有焦虑，任何时代、任何企业、任何人都有焦虑，只是有的人敏感一些，有的人不敏感。我对未来的判断：远的一定好，近期很困难，中期更困难。现在是飞机穿过云层的颠簸期，所以大家要把握好方向盘，系好安全带，但最重要的是要有未来观，当你从未来看今天的时候，今天的困难不是困难，当你只看着今天，跟昨天比较的时候，你的沮丧会越来越大。所以我们要面向未来，用未来的方法来解决今天的问题，而不要用昨天的方法来解决今天的问题。很多人非常乐观，很多人又非常悲观，我认为过度乐观、过度悲观都是因为没有看清楚未来。昨天的看法和行动决定了你企业的今天，而今天的看法和思考又决定了你企业的明天。未来不是指两年以后你会什么样，未来是指二十年以后、三十年以后，你的企业会变成什么样。在座每个企业，你们今天的规模、你们今天的成就，我们今天所取得的一切不是今天做的，都是因为二十年以前我们相信中国会有今天。我相信当你从今天去判断二十年以后这个世界会变成怎么样，社会会变成怎么样，去解决未来的问题，相信年轻人，你一定会有前途。在今天所有巨大的不确定当中，我认为数字化是确定的，数字化一定会全面改造所有的行业。不是每家企业都要转型，但是每个企业都必须升级，完成数字化升级。现在不要担心跟你竞争了二十年、十年的“老王”，你要担心的是那些你完全不认识、没见过到的 “小李”，这批人从来没出现过，他们没有包袱，他们采用新的技术，敢于创新，敢于用互联网。这些年轻人将是中国的希望，但也可能是在座每个人可能碰上的压力，最后打败各位的不是互联网企业，而是用好互联网技术的企业。我希望所有的企业今天一定要思考，要利用数据化来升级自己的管理、升级自己的组织、升级自己的产品。未来十年，是传统行业推进数字化的最后十年。其实我在二十年前，在我去过的很多的省份，不断在讲互联网对于零售行业的冲击、对制造行业的冲击，大家并没有当回事情，直到淘宝、天猫、阿里巴巴、百度这些企业、腾讯这些企业起来以后，大家突然觉得“狼”来了，其实“狼”一直都在。如果今天不准备变革，十年后我相信你一定是数字脱贫的对象。直到今天，很遗憾的是，还有很多企业家依然在讲数字革命是一场危言耸听，数字技术、互联网技术只是简单的工具。我想告诉大家，数字技术将重新定义生产制造，重新定义零售，重新定义技术，重新定义生产资料和一切，甚至很快这个技术将会引发全社会的所有生产关系的变革。其实未来越是距离技术远的行业，越是还没有被技术改造的行业，机会越大。很多企业其实只要投入更多技术，在人才，在理念上多一点点，获得的收益和价值将是非常确定的。今天你纯粹做一家互联网公司，你未必有回报，但是你今天把自己传统企业变成数字化，你的回报是肯定的。传统行业的数字化转型关键在于观念，关键在于理念，责任一定在一把手身上，责任不在技术部门，一把手不改变的企业一定不是有远见，有担当的企业。疫情总会结束，但是新冠这种疾病可能会伴随人类几百年甚至上千年，现在这个病毒恐怕是会比我们谁都活得长，我们要学会适应，我们的防疫也要学会从常规防疫变成常态防疫，也许以后绝大部分的医院都有一个“新冠科”，这是长期共存可能会出现的情况。2020年注定是改变历史的一年，在武汉的时候，我们CEC的企业家们给自己提了这样的要求：企业家不等于有钱人，有钱人更不一定是企业家。企业家是国家、民族的优质资产，国难当头时，企业家们无须命令，自觉自愿，挺身而出。疫情是突发的灾难，我们必须主动担当；疫情结束以后的经济恢复，企业家更是责无旁贷。所有的企业，在座的企业家们，如果你的企业在十年、二十年内没有经历过三次、五次的灾难，你的企业永远是经不起打击的。越是在世界剧变的时候，越是在历史的转折点上，企业家越要担当，我们做这些事情，不是为了赢得掌声，不是为了赢得肯定，不是为了赢得一些政策的支持，而是我们内心相信、我们喜欢、我们认定，应该用未来的眼光，用战略的眼光，与世界去沟通，通过我们自己的努力，让这个世界变得越来越好。
AI摘要 本文是一篇关于Golang汇编入门的知识总结，作者分享了自己在学习Golang底层实现机制时对Golang汇编的理解。文章首先解释了为什么需要汇编语言，然后介绍了程序与进程的概念，接着深入讲解了Go汇编的相关知识，包括核心寄存器、常用操作指令等。文章还提供了如何输出Go汇编的方法，并通过对一个简单Go函数的汇编代码进行分析，帮助读者理解Go汇编的实现过程。最后，文章还介绍了一些Go调试工具，如gdb和delve，并总结了学习Go汇编需要熟悉的几个方面。 document.addEventListener('DOMContentLoaded', function() { const urlDisplay = document.getElementById('urlDisplay'); urlDisplay.textContent = "此内容根据文章生成，更内容请访问：" + window.location.href; }); 作者：ivansli，腾讯 IEG 运营开发工程师在深入学习 Golang 的 runtime 和标准库实现的时候发现，如果对 Golang 汇编没有一定了解的话，很难深入了解其底层实现机制。在这里整理总结了一份基础的 Golang 汇编入门知识，通过学习之后能够对其底层实现有一定的认识。0. 为什么写本文平时业务中一直使用 PHP 编写代码，但是一直对 Golang 比较感兴趣，闲暇、周末之余会看一些 Go 底层源码。近日在分析 go 的某些特性底层功能实现时发现：有些又跟 runtime 运行时有关，而要掌握这一部分的话，有一道坎是绕不过去的，那就是 Go 汇编。索性就查阅了很多大佬们写的资料，在阅读之余整理总结了一下，并在这里分享给大家。本文使用 Go 版本为 go1.14.11. 为什么需要汇编众所周知，在计算机的世界里，只有 2 种类型。那就是：0 和 1。计算机工作是由一系列的机器指令进行驱动的，这些指令又是一组二进制数字，其对应计算机的高低电平。而这些机器指令的集合就是机器语言，这些机器语言在最底层是与硬件一一对应的。显而易见，这样的机器指令有一个致命的缺点：可阅读性太差（恐怕也只有天才和疯子才有能力把控得了）。为了解决可读性的问题以及代码编辑的需求，于是就诞生了最接近机器的语言：汇编语言（在我看来，汇编语言更像一种助记符，这些人们容易记住的每一条助记符都映射着一条不容易记住的由 0、1 组成的机器指令。你觉得像不像域名与 IP 地址的关系呢？）。1.1 程序的编译过程以 C 语言为例来说，从 hello.c 的源码文件到 hello 可执行文件，经过编译器处理，大致分为几个阶段：编译器在不同的阶段会做不同的事情，但是有一步是可以确定的，那就是：源码会被编译成汇编，最后才是二进制。2. 程序与进程源码经过编译之后，得到一个二进制的可执行 文件。文件这两个字也就表明，目前得到的这个文件跟其他文件对比，除了是具有一定的格式（Linux 中是 ELF 格式，即：可运行可链接。executable linkable formate）的二进制组成，并没什么区别。在 Linux 中文件类型大致分为 7 种：b: 块设备文件c：字符设备文件d：目录-：普通文件l：链接s：socketp：管道通过上面可以看到，可执行文件 main 与源码文件 main.go，都是同一种类型，属于普通文件。（当然了，在 Unix 中有一句很经典的话：一切皆文件）。那么，问题来了：什么是程序？什么是进程？2.1 程序维基百科告诉我们：程序是指一组指示计算机或其他具有消息处理能力设备每一步动作的指令，通常用某种程序设计语言编写，运行于某种目标体系结构上。从某个层面来看，可以把程序分为静态程序、动态程序：静态程序：单纯的指具有一定格式的可执行二进制文件。动态程序：则是静态可执行程序文件被加载到内存之后的一种运行时模型（又称为进程）。2.2 进程首先，要知道的是，进程是分配系统资源的最小单位，线程(带有时间片的函数)是系统调度的最小单位。进程包含线程，线程所属于进程。创建进程一般使用 fork 方法(通常会有个拉起程序，先 fork 自身生成一个子进程。然后，在该子进程中通过 exec 函数把对应程序加载进来，进而启动目标进程。当然，实际上要复杂得多)，而创建线程则是使用 pthread 线程库。以 32 位 Linux 操作系统为例，进程经典的虚拟内存结构模型如下图所示：其中，有两处结构是静态程序所不具有的，那就是 运行时堆(heap)与 运行时栈(stack)。运行时堆从低地址向高地址增长，申请的内存空间需要程序员自己或者由 GC 释放。运行时栈从高地址向低地址增长，内存空间在当前栈桢调用结束之后自动释放(并不是清除其所占用内存中数据，而是通过栈顶指针 SP 的移动，来标识哪些内存是正在使用的)。3. Go 汇编对于 Go 编译器而言，其输出的结果是一种抽象可移植的汇编代码，这种汇编（Go 的汇编是基于 Plan9 的汇编）并不对应某种真实的硬件架构。Go 的汇编器会使用这种伪汇编，再为目标硬件生成具体的机器指令。伪汇编这一个额外层可以带来很多好处，最主要的一点是方便将 Go 移植到新的架构上。相关的信息可以参考 Rob Pike 的 The Design of the Go Assembler。要了解 Go 的汇编器最重要的是要知道 Go 的汇编器不是对底层机器的直接表示，即 Go 的汇编器没有直接使用目标机器的汇编指令。Go 汇编器所用的指令，一部分与目标机器的指令一一对应，而另外一部分则不是。这是因为编译器套件不需要汇编器直接参与常规的编译过程。相反，编译器使用了一种半抽象的指令集，并且部分指令是在代码生成后才被选择的。汇编器基于这种半抽象的形式工作，所以虽然你看到的是一条 MOV 指令，但是工具链针对对这条指令实际生成可能完全不是一个移动指令，也许会是清除或者加载。也有可能精确的对应目标平台上同名的指令。概括来说，特定于机器的指令会以他们的本尊出现， 然而对于一些通用的操作，如内存的移动以及子程序的调用以及返回通常都做了抽象。细节因架构不同而不一样，我们对这样的不精确性表示歉意，情况并不明确。汇编器程序的工作是对这样半抽象指令集进行解析并将其转变为可以输入到链接器的指令。The most important thing to know about Go’s assembler is that it is not a direct representation of the underlying machine. Some of the details map precisely to the machine, but some do not. This is because the compiler suite needs no assembler pass in the usual pipeline. Instead, the compiler operates on a kind of semi-abstract instruction set, and instruction selection occurs partly after code generation. The assembler works on the semi-abstract form, so when you see an instruction like MOV what the toolchain actually generates for that operation might not be a move instruction at all, perhaps a clear or load.Or it might correspond exactly to the machine instruction with that name. In general, machine-specific operations tend to appear as themselves, while more general concepts like memory move and subroutine call and return are more abstract. The details vary with architecture, and we apologize for the imprecision; the situation is not well-defined.The assembler program is a way to parse a description of that semi-abstract instruction set and turn it into instructions to be input to the linker.Go 汇编使用的是 caller-save模式，被调用函数的入参参数、返回值都由调用者维护、准备。因此，当需要调用一个函数时，需要先将这些工作准备好，才调用下一个函数，另外这些都需要进行内存对齐，对齐的大小是 sizeof(uintptr)。3.1 几个概念在深入了解 Go 汇编之前，需要知道的几个概念：栈：进程、线程、goroutine 都有自己的调用栈，先进后出（FILO）栈帧：可以理解是函数调用时，在栈上为函数所分配的内存区域调用者：caller，比如：A 函数调用了 B 函数，那么 A 就是调用者被调者：callee，比如：A 函数调用了 B 函数，那么 B 就是被调者3.2 Go 的核心寄存器go 汇编中有 4 个核心的伪寄存器，这 4 个寄存器是编译器用来维护上下文、特殊标识等作用的：寄存器说明SB(Static base pointer)global symbolsFP(Frame pointer)arguments and localsPC(Program counter)jumps and branchesSP(Stack pointer)top of stackFP: 使用如symbol+offset(FP)的方式，引用 callee 函数的入参参数。例如arg0+0(FP)，arg1+8(FP)，使用 FP 必须加 symbol ，否则无法通过编译(从汇编层面来看，symbol 没有什么用，加 symbol 主要是为了提升代码可读性)。另外，需要注意的是：往往在编写 go 汇编代码时，要站在 callee 的角度来看(FP)，在 callee 看来，(FP)指向的是 caller 调用 callee 时传递的第一个参数的位置。假如当前的 callee 函数是 add，在 add 的代码中引用 FP，该 FP 指向的位置不在 callee 的 stack frame 之内。而是在 caller 的 stack frame 上，指向调用 add 函数时传递的第一个参数的位置，经常在 callee 中用symbol+offset(FP)来获取入参的参数值。SB: 全局静态基指针，一般用在声明函数、全局变量中。SP: 该寄存器也是最具有迷惑性的寄存器，因为会有伪 SP 寄存器和硬件 SP 寄存器之分。plan9 的这个伪 SP 寄存器指向当前栈帧第一个局部变量的结束位置(为什么说是结束位置，可以看下面寄存器内存布局图)，使用形如 symbol+offset(SP) 的方式，引用函数的局部变量。offset 的合法取值是 [-framesize, 0)，注意是个左闭右开的区间。假如局部变量都是 8 字节，那么第一个局部变量就可以用 localvar0-8(SP) 来表示。与硬件寄存器 SP 是两个不同的东西，在栈帧 size 为 0 的情况下，伪寄存器 SP 和硬件寄存器 SP 指向同一位置。手写汇编代码时，如果是 symbol+offset(SP)形式，则表示伪寄存器 SP。如果是 offset(SP)则表示硬件寄存器 SP。务必注意：对于编译输出(go tool compile -S / go tool objdump)的代码来讲，所有的 SP 都是硬件 SP 寄存器，无论是否带 symbol（这一点非常具有迷惑性，需要慢慢理解。往往在分析编译输出的汇编时，看到的就是硬件 SP 寄存器）。PC: 实际上就是在体系结构的知识中常见的 pc 寄存器，在 x86 平台下对应 ip 寄存器，amd64 上则是 rip。除了个别跳转之外，手写 plan9 汇编代码时，很少用到 PC 寄存器。通过上面的讲解，想必已经对 4 个核心寄存器的区别有了一定的认识（或者是更加的迷惑、一头雾水）。那么，需要留意的是：如果是在分析编译输出的汇编代码时，要重点看 SP、SB 寄存器（FP 寄存器在这里是看不到的）。如果是，在手写汇编代码，那么要重点看 FP、SP 寄存器。3.2.1 伪寄存器的内存模型下图描述了栈桢与各个寄存器的内存关系模型，值得注意的是要站在 callee 的角度来看。有一点需要注意的是，return addr 也是在 caller 的栈上的，不过往栈上插 return addr 的过程是由 CALL 指令完成的（在分析汇编时，是看不到关于 addr 相关空间信息的。在分配栈空间时，addr 所占用空间大小不包含在栈帧大小内）。在 AMD64 环境，伪 PC 寄存器其实是 IP 指令计数器寄存器的别名。伪 FP 寄存器对应的是 caller 函数的帧指针，一般用来访问 callee 函数的入参参数和返回值。伪 SP 栈指针对应的是当前 callee 函数栈帧的底部（不包括参数和返回值部分），一般用于定位局部变量。伪 SP 是一个比较特殊的寄存器，因为还存在一个同名的 SP 真寄存器，真 SP 寄存器对应的是栈的顶部。在编写 Go 汇编时，当需要区分伪寄存器和真寄存器的时候只需要记住一点：伪寄存器一般需要一个标识符和偏移量为前缀，如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真 SP 寄存器，而 a(SP)、b+8(SP)有标识符为前缀表示伪寄存器。3.2.2 几点说明我们这里对容易混淆的几点简单进行说明：伪 SP 和硬件 SP 不是一回事，在手写汇编代码时，伪 SP 和硬件 SP 的区分方法是看该 SP 前是否有 symbol。如果有 symbol，那么即为伪寄存器，如果没有，那么说明是硬件 SP 寄存器。伪 SP 和 FP 的相对位置是会变的，所以不应该尝试用伪 SP 寄存器去找那些用 FP+offset 来引用的值，例如函数的入参和返回值。官方文档中说的伪 SP 指向 stack 的 top，可能是有问题的。其指向的局部变量位置实际上是整个栈的栈底（除 caller BP 之外），所以说 bottom 更合适一些。在 go tool objdump/go tool compile -S 输出的代码中，是没有伪 SP 和 FP 寄存器的，我们上面说的区分伪 SP 和硬件 SP 寄存器的方法，对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中，只有真实的 SP 寄存器。3.2.3 IA64 和 plan9 的对应关系在 plan9 汇编里还可以直接使用的 amd64 的通用寄存器，应用代码层面会用到的通用寄存器主要是: rax, rbx, rcx, rdx, rdi, rsi, r8~r15 这些寄存器，虽然 rbp 和 rsp 也可以用，不过 bp 和 sp 会被用来管理栈顶和栈底，最好不要拿来进行运算。plan9 中使用寄存器不需要带 r 或 e 的前缀，例如 rax，只要写 AX 即可: MOVQ $101, AX = mov rax, 101下面是通用通用寄存器的名字在 IA64 和 plan9 中的对应关系:3.3 常用操作指令下面列出了常用的几个汇编指令（指令后缀 Q 说明是 64 位上的汇编指令）助记符指令种类用途示例MOVQ传送数据传送MOVQ 48, AX // 把 48 传送到 AXLEAQ传送地址传送LEAQ AX, BX // 把 AX 有效地址传送到 BXPUSHQ传送栈压入PUSHQ AX // 将 AX 内容送入栈顶位置POPQ传送栈弹出POPQ AX // 弹出栈顶数据后修改栈顶指针ADDQ运算相加并赋值ADDQ BX, AX // 等价于 AX+=BXSUBQ运算相减并赋值SUBQ BX, AX // 等价于 AX-=BXCMPQ运算比较大小CMPQ SI CX // 比较 SI 和 CX 的大小CALL转移调用函数CALL runtime.printnl(SB) // 发起调用JMP转移无条件转移指令JMP 0x0185 //无条件转至 0x0185 地址处JLS转移条件转移指令JLS 0x0185 //左边小于右边，则跳到 0x01854. 汇编分析说了那么多，it is code show time。4.1 如何输出 Go 汇编对于写好的 go 源码，生成对应的 Go 汇编，大概有下面几种方法 1 先使用go build -gcflags "-N -l" main.go 生成对应的可执行二进制文件 再使用go tool objdump -s "main\." main 反编译获取对应的汇编反编译时 "main\." 表示只输出 main 包中相关的汇编 "main\.main" 则表示只输出 main 包中 main 方法相关的汇编方法 2 使用go tool compile -S -N -l main.go 这种方式直接输出汇编方法 3 使用go build -gcflags="-N -l -S" main.go 直接输出汇编注意：在使用这些命令时，加上对应的 flag，否则某些逻辑会被编译器优化掉，而看不到对应完整的汇编代码-l 禁止内联 -N 编译时，禁止优化 -S 输出汇编代码4.2 Go 汇编示例go 示例代码package main func add(a, b int) int{ sum := 0 // 不设置该局部变量sum，add栈空间大小会是0 sum = a+b return sum} func main(){ println(add(1,2))}编译 go 源代码，输出汇编go tool compile -N -l -S main.go截取主要汇编如下："".add STEXT nosplit size=60 args=0x18 locals=0x10 0x0000 00000 (main.go:3) TEXT "".add(SB), NOSPLIT, $16-24 0x0000 00000 (main.go:3) SUBQ $16, SP ;;生成add栈空间 0x0004 00004 (main.go:3) MOVQ BP, 8(SP) 0x0009 00009 (main.go:3) LEAQ 8(SP), BP ;; ...omitted FUNCDATA stuff... 0x000e 00014 (main.go:3) MOVQ $0, "".~r2+40(SP) ;;初始化返回值 0x0017 00023 (main.go:4) MOVQ $0, "".sum(SP) ;;局部变量sum赋为0 0x001f 00031 (main.go:5) MOVQ "".a+24(SP), AX ;;取参数a 0x0024 00036 (main.go:5) ADDQ "".b+32(SP), AX ;;等价于AX=a+b 0x0029 00041 (main.go:5) MOVQ AX, "".sum(SP) ;;赋值局部变量sum 0x002d 00045 (main.go:6) MOVQ AX, "".~r2+40(SP) ;;设置返回值 0x0032 00050 (main.go:6) MOVQ 8(SP), BP 0x0037 00055 (main.go:6) ADDQ $16, SP ;;清除add栈空间 0x003b 00059 (main.go:6) RET ...... "".main STEXT size=107 args=0x0 locals=0x28 0x0000 00000 (main.go:9) TEXT "".main(SB), $40-0 ...... 0x000f 00015 (main.go:9) SUBQ $40, SP ;; 生成main栈空间 0x0013 00019 (main.go:9) MOVQ BP, 32(SP) 0x0018 00024 (main.go:9) LEAQ 32(SP), BP ;; ...omitted FUNCDATA stuff... 0x001d 00029 (main.go:10) MOVQ $1, (SP) ;;add入参：1 0x0025 00037 (main.go:10) MOVQ $2, 8(SP) ;;add入参：2 0x002e 00046 (main.go:10) CALL "".add(SB) ;;调用add函数 0x0033 00051 (main.go:10) MOVQ 16(SP), AX 0x0038 00056 (main.go:10) MOVQ AX, ""..autotmp_0+24(SP) 0x003d 00061 (main.go:10) CALL runtime.printlock(SB) 0x0042 00066 (main.go:10) MOVQ ""..autotmp_0+24(SP), AX 0x0047 00071 (main.go:10) MOVQ AX, (SP) 0x004b 00075 (main.go:10) CALL runtime.printint(SB) 0x0050 00080 (main.go:10) CALL runtime.printnl(SB) 0x0055 00085 (main.go:10) CALL runtime.printunlock(SB) 0x005a 00090 (main.go:11) MOVQ 32(SP), BP 0x005f 00095 (main.go:11) ADDQ $40, SP ;;清除main栈空间 0x0063 00099 (main.go:11) RET ......这里列举了一个简单的 int 类型 加法示例，实际开发中会遇到各种参数类型，要复杂的多，这里只是抛砖引玉 :)4.3 Go 汇编解析针对 4.2 输出汇编，对重要核心代码进行分析。4.3.1 add 函数汇编解析TEXT "".add(SB), NOSPLIT|ABIInternal, $16-24TEXT "".add TEXT 指令声明了 "".add 是 .text 代码段的一部分，并表明跟在这个声明后的是函数的函数体。在链接期，""这个空字符会被替换为当前的包名: 也就是说，"".add 在链接到二进制文件后会变成 main.add(SB) SB 是一个虚拟的伪寄存器，保存静态基地址(static-base) 指针，即我们程序地址空间的开始地址。"".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲，它有一个直接的绝对地址: 是一个全局的函数符号。NOSPLIT: 向编译器表明不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下，编译器自己帮我们插入了这个标记: 它足够聪明地意识到，由于 add 没有任何局部变量且没有它自己的栈帧，所以一定不会超出当前的栈。不然，每次调用函数时，在这里执行栈检查就是完全浪费 CPU 时间了。$0-1624 指定了调用方传入的参数+返回值大小（24 字节=入参 a、b 大小 8字节*2+ 返回值8字节）> 通常来讲，帧大小后一般都跟随着一个参数大小，用减号分隔。(这不是一个减法操作，只是一种特殊的语法) 帧大小 $24-8 意味着这个函数有 24 个字节的帧以及 8 个字节的参数，位于调用者的帧上。如果 NOSPLIT 没有在 TEXT 中指定，则必须提供参数大小。对于 Go 原型的汇编函数，go vet 会检查参数大小是否正确。In the general case, the frame size is followed by an argument size, separated by a minus sign. (It’s not a subtraction, just idiosyncratic syntax.) The frame size $24-8 states that the function has a 24-byte frame and is called with 8 bytes of argument, which live on the caller’s frame. If NOSPLIT is not specified for the TEXT, the argument size must be provided. For assembly functions with Go prototypes, go vet will check that the argument size is correct.SUBQ $16, SPSP 为栈顶指针，该语句等价于 SP-=16（由于栈空间是向下增长的，所以开辟栈空间时为减操作），表示生成 16 字节大小的栈空间。MOVQ $0, "".~r2+40(SP)此时的 SP 为 add 函数栈的栈顶指针，40(SP)的位置则是 add 返回值的位置，该位置位于 main 函数栈空间内。该语句设置返回值类型的 0 值，即初始化返回值，防止得到脏数据（返回值类型为 int，int 的 0 值为 0）。MOVQ "".a+24(SP), AX从 main 函数栈空间获取入参 a 的值，存到寄存器 AXADDQ "".b+32(SP), AX从 main 函数栈空间获取入参 b 的值，与寄存器 AX 中存储的 a 值相加，结果存到 AX。相当于 AX=a+bMOVQ AX, "".~r2+40(SP)把 a+b 的结果放到 main 函数栈中, add(a+b)返回值所在的位置ADDQ $16, SP归还 add 函数占用的栈空间4.3.2 函数栈桢结构模型根据 4.2 对应汇编绘制的函数栈桢结构模型还记得前面提到的，Go 汇编使用的是 caller-save模式，被调用函数的参数、返回值、栈位置都需要由调用者维护、准备吗？在函数栈桢结构中可以看到，add()函数的入参以及返回值都由调用者 main()函数维护。也正是因为如此，GO 有了其他语言不具有的，支持多个返回值的特性。4.4 Go 汇编语法这里重点讲一下函数声明、变量声明。4.4.1 函数声明来看一个典型的 Go 汇编函数定义// func add(a, b int) int// 该add函数声明定义在同一个 package name 下的任意 .go文件中// 只有函数头，没有实现 // add函数的Go汇编实现// pkgname 默认是 ""TEXT pkgname·add(SB), NOSPLIT, $16-24 MOVQ a+0(FP), AX ADDQ b+8(FP), AX MOVQ AX, ret+16(FP) RETGo 汇编实现为什么是 TEXT 开头？仔细观察上面的进程内存布局图就会发现，我们的代码在是存储在.text 段中的，这里也就是一种约定俗成的起名方式。实际上在 plan9 中 TEXT 是一个指令，用来定义一个函数。定义中的 pkgname 是可以省略的，(非想写也可以写上，不过写上 pkgname 的话，在重命名 package 之后还需要改代码，默认为 "") 编译器会在链接期自动加上所属的包名称。中点 · 比较特殊，是一个 unicode 的中点，该点在 mac 下的输入方法是 option+shift+9。在程序被链接之后，所有的中点 ·都会被替换为句号 .，比如你的方法是 runtime·main，在编译之后的程序里的符号则是 runtime.main。简单总结一下, Go 汇编实现函数声明，格式为: 静态基地址(static-base) 指针 | | add函数入参+返回值总大小 | |TEXT pkgname·add(SB),NOSPLIT,$16-24 | | |函数所属包名 函数名 add函数栈帧大小函数栈帧大小：局部变量+可能需要的额外调用函数的参数空间的总大小，不包括调用其它函数时的 ret address 的大小。(SB): SB 是一个虚拟寄存器，保存了静态基地址(static-base) 指针，即我们程序地址空间的开始地址。"".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲，它有一个直接的绝对地址: 是一个全局的函数符号。NOSPLIT: 向编译器表明，不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下，编译器自己帮我们插入了这个标记: 它足够聪明地意识到，add 不会超出当前的栈，因此没必要调用函数时在这里执行栈检查。4.4.2 变量声明汇编里的全局变量，一般是存储在 .rodata或者 .data段中。对应到 Go 代码，就是已初始化过的全局的 const、var 变量/常量。使用 DATA 结合 GLOBL 来定义一个变量。DATA 的用法为:DATA symbol+offset(SB)/width, value大多数参数都是字面意思，不过这个 offset 需要注意：其含义是该值相对于符号 symbol 的偏移，而不是相对于全局某个地址的偏移。GLOBL 汇编指令用于定义名为 symbol 的全局变量，变量对应的内存宽度为 width，内存宽度部分必须用常量初始化。GLOBL ·symbol(SB), width下面是定义了多个变量的例子:DATA ·age+0(SB)/4, $8 ;; 数值8为 4字节GLOBL ·age(SB), RODATA, $4 DATA ·pi+0(SB)/8, $3.1415926 ;; 数值3.1415926为float64, 8字节GLOBL ·pi(SB), RODATA, $8 DATA ·year+0(SB)/4, $2020 ;; 数值2020为 4字节GLOBL ·year(SB), RODATA, $4 ;; 变量hello 使用2个DATA来定义DATA ·hello+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello+8(SB)/8, $" world" ;; ` world` 共8个字节(3个空格)GLOBL ·hello(SB), RODATA, $16 ;; `hello my world` 共16个字节 DATA ·hello<>+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello<>+8(SB)/8, $" world" ;; ` world` 共8个字节(3个空格)GLOBL ·hello<>(SB), RODATA, $16 ;; `hello my world` 共16个字节大部分都比较好理解，不过这里引入了新的标记 <>，这个跟在符号名之后，表示该全局变量只在当前文件中生效，类似于 C 语言中的 static。如果在另外文件中引用该变量的话，会报 relocation target not found 的错误。5. 手写汇编实现功能在 Go 源码中会看到一些汇编写的代码，这些代码跟其他 go 代码一起组成了整个 go 的底层功能实现。下面，我们通过一个简单的 Go 汇编代码示例来实现两数相加功能。5.1 使用 Go 汇编实现 add 函数Go 代码package main func add(a, b int64) int64 func main(){ println(add(2,3))}Go 源码中 add()函数只有函数签名，没有具体的实现（使用 GO 汇编实现）使用 Go 汇编实现的 add()函数TEXT ·add(SB), $0-24 ;; add栈空间为0，入参+返回值大小=24字节 MOVQ x+0(FP), AX ;; 从main中取参数：2 ADDQ y+8(FP), AX ;; 从main中取参数：3 MOVQ AX, ret+16(FP) ;; 保存结果到返回值 RET把 Go 源码与 Go 汇编编译到一起(我这里，这两个文件在同一个目录)go build -gcflags "-N -l" .我这里目录为 demo1，所以得到可执行程序 demo1，运行得到结果：55.2 反编译可执行程序对 5.1 中得到的可执行程序 demo1 使用 objdump 进行反编译，获取汇编代码go tool objdump -s "main\." demo1得到汇编......TEXT main.main(SB) /root/go/src/demo1/main.go main.go:5 0x4581d0 64488b0c25f8ffffff MOVQ FS:0xfffffff8, CX main.go:5 0x4581d9 483b6110 CMPQ 0x10(CX), SP main.go:5 0x4581dd 7655 JBE 0x458234 main.go:5 0x4581df 4883ec28 SUBQ $0x28, SP ;;生成main栈桢 main.go:5 0x4581e3 48896c2420 MOVQ BP, 0x20(SP) main.go:5 0x4581e8 488d6c2420 LEAQ 0x20(SP), BP main.go:6 0x4581ed 48c7042402000000 MOVQ $0x2, 0(SP) ;;参数值 2 main.go:6 0x4581f5 48c744240803000000 MOVQ $0x3, 0x8(SP) ;;参数值 3 main.go:6 0x4581fe e83d000000 CALL main.add(SB);;call add main.go:6 0x458203 488b442410 MOVQ 0x10(SP), AX main.go:6 0x458208 4889442418 MOVQ AX, 0x18(SP) main.go:6 0x45820d e8fe2dfdff CALL runtime.printlock(SB) main.go:6 0x458212 488b442418 MOVQ 0x18(SP), AX main.go:6 0x458217 48890424 MOVQ AX, 0(SP) main.go:6 0x45821b e87035fdff CALL runtime.printint(SB) main.go:6 0x458220 e87b30fdff CALL runtime.printnl(SB) main.go:6 0x458225 e8662efdff CALL runtime.printunlock(SB) main.go:7 0x45822a 488b6c2420 MOVQ 0x20(SP), BP main.go:7 0x45822f 4883c428 ADDQ $0x28, SP main.go:7 0x458233 c3 RET main.go:5 0x458234 e89797ffff CALL runtime.morestack_noctxt(SB) main.go:5 0x458239 eb95 JMP main.main(SB) ;; 反编译得到的汇编与add_amd64.s文件中的汇编大致操作一致TEXT main.add(SB) /root/go/src/demo1/add_amd64.s add_amd64.s:2 0x458240 488b442408 MOVQ 0x8(SP), AX ;; 获取第一个参数 add_amd64.s:3 0x458245 4803442410 ADDQ 0x10(SP), AX ;;参数a+参数b add_amd64.s:5 0x45824a 4889442418 MOVQ AX, 0x18(SP) ;;保存计算结果 add_amd64.s:7 0x45824f c3 RET通过上面操作，可知：(FP)伪寄存器，只有在编写 Go 汇编代码时使用。FP 伪寄存器指向 caller 传递给 callee 的第一个参数使用 go tool compile / go tool objdump 得到的汇编中看不到(FP)寄存器的踪影6. Go 调试工具这里推荐 2 个 Go 代码调试工具。6.1 gdb 调试 Go 代码测试代码package main type Ier interface{ add(a, b int) int sub(a, b int) int} type data struct{ a, b int} func (*data) add(a, b int) int{ return a+b} func (*data) sub(a, b int) int{ return a-b} func main(){ var t Ier = &data{3,4} println(t.add(1,2)) println(t.sub(3,2))}编译 go build -gcflags "-N -l" -o main使用 GDB 调试> gdb main GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.htmlThis is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law. Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /root/go/src/interface/main...done.Loading Go Runtime support.(gdb) list // 显示源码14 func (*data) add(a, b int) int{15 return a+b16 }1718 func (*data) sub(a, b int) int{19 return a-b20 }212223 func main(){(gdb) list24 var t Ier = &data{3,4}2526 println(t.add(1,2))27 println(t.sub(3,2))28 }29(gdb) b 26 // 在源码26行处设置断点Breakpoint 1 at 0x45827c: file /root/go/src/interface/main.go, line 26.(gdb) rStarting program: /root/go/src/interface/main Breakpoint 1, main.main () at /root/go/src/interface/main.go:2626 println(t.add(1,2))(gdb) info locals // 显示变量t = {tab = 0x487020 <data,main.Ier>, data = 0xc000096000}(gdb) ptype t // 打印t的结构type = struct runtime.iface { runtime.itab *tab; void *data;}(gdb) p *t.tab.inter // 打印t.tab.inter指针指向的数据$2 = {typ = {size = 16, ptrdata = 16, hash = 2491815843, tflag = 7 '\a', align = 8 '\b', fieldAlign = 8 '\b', kind = 20 '\024', equal = {void (void *, void *, bool *)} 0x466ec0, gcdata = 0x484351 "\002\003\004\005\006\a\b\t\n\f\r\016\017\020\022\025\026\030\033\034\036\037\"&(,-5<BUXx\216\231\330\335\377", str = 6568, ptrToThis = 23808}, pkgpath = {bytes = 0x4592b4 ""}, mhdr = []runtime.imethod = {{name = 277, ityp = 48608}, {name = 649, ityp = 48608}}}(gdb) disass // 显示汇编Dump of assembler code for function main.main: 0x0000000000458210 <+0>: mov %fs:0xfffffffffffffff8,%rcx 0x0000000000458219 <+9>: cmp 0x10(%rcx),%rsp 0x000000000045821d <+13>: jbe 0x458324 <main.main+276> 0x0000000000458223 <+19>: sub $0x50,%rsp 0x0000000000458227 <+23>: mov %rbp,0x48(%rsp) 0x000000000045822c <+28>: lea 0x48(%rsp),%rbp 0x0000000000458231 <+33>: lea 0x10dc8(%rip),%rax # 0x469000 0x0000000000458238 <+40>: mov %rax,(%rsp) 0x000000000045823c <+44>: callq 0x40a5c0 <runtime.newobject>常用的 gdb 调试命令runcontinuebreakbacktrace 与 frameinfo break、localslist 命令print 和 ptype 命令disass除了 gdb，另外推荐一款 gdb 的增强版调试工具 cgdbhttps://cgdb.github.io/效果如下图所示，分两个窗口：上面显示源代码，下面是具体的命令行调试界面(跟 gdb 一样)：#### 6.2 delve 调试代码delve 项目地址https://github.com/go-delve/delve带图形化界面的 dlv 项目地址https://github.com/aarzilli/gdlvdlv 的安装使用，这里不再做过多讲解，感兴趣的可以尝试一下。gdb 作为调试工具自是不用多说，比较老牌、强大，可以支持多种语言。delve 则是使用 go 语言开发的，用来调试 go 的工具，功能也是十分强大，打印结果可以显示 gdb 支持不了的东西，这里不再做过多讲解，有兴趣的可以查阅相关资料。7. 总结对于 Go 汇编基础大致需要熟悉下面几个方面：通过上面的例子相信已经让你对 Go 的汇编有了一定的理解。当然，对于大部分业务开发人员来说，只要看的懂即可。如果想进一步的了解，可以阅读相关的资料或者书籍。最后想说的是：鉴于个人能力有限，在阅读过程中你可能会发现存在的一些问题或者缺陷，欢迎各位大佬指正。如果感兴趣的话，也可以一起私下交流。8. 参考资料在整理的过程中，部分参考、引用下面链接地址内容。有一些写的还是不错的，感兴趣的同学可以阅读。[1] https://github.com/cch123/golang-notes/blob/master/assembly.md plan9 assembly[2] https://segmentfault.com/a/1190000019753885 汇编入门[3] https://www.davidwong.fr/goasm/ Go Assembly by Example[4] https://juejin.im/post/6844904005630443533#heading-3[5] https://github.com/go-internals-cn/go-internals/blob/master/chapter1_assembly_primer/README.md[6] https://lrita.github.io/2017/12/12/golang-asm/[7] https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-01-basic.html
用了多年的自动升级小工具《KDBOSS升级助手》
文章介绍了一个名为《KDBOSS升级助手》的自动升级小工具，该工具由作者8年前编写，用于客户端软件升级，具有稳定性和易用性。主要功能包括：采用idhttp下载、使用ini作为配置文件、支持下载进度百分比显示、支持下载速率显示以及支持自动下载和自动更新。文章提供了工具的源码下载链接，并附上了详细的代码...[...]
轻武CRM微客是一款基于微信的营销SASS软件，通过微信大数据帮助用户拓客、分享知识、筛选和挖掘潜在客户，实现精准营销。适用于微商、小微创业者、保险销售等全行业销售人员。最近更新包括支持全网URL地址转发、服务号和订阅号文章内视频音频文件处理、新增文章内容打赏功能、完成订单系统优化、推送消息优化和爬...[...]