1/* 2 * Copyright (c) 2021-2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16const commander = require('commander'); 17const program = new commander.Command(); 18const path = require('path'); 19const fs = require('fs'); 20const nodeCmd = require('node-cmd'); 21 22class ModulesFetcher { 23 // Path to hdc, or path to hdc with hdc server ip. 24 private hdc_: string | undefined; 25 private pathChecksumMap_: Map<string, number>; 26 27 private moduleFile_: string = ''; 28 private outputDir_: string = ''; 29 private appName_: string = ''; 30 31 constructor() { 32 this.pathChecksumMap_ = new Map<string, number>(); 33 } 34 35 private parseCliArgs(): boolean { 36 program 37 .description('Fetch libraries from device in order to use them to create valide flamegraph.'); 38 program 39 .requiredOption('-m, --module_file <type>', 'Input file with modules paths.') 40 .requiredOption('-o, --output_dir <type>', 'Directory in which libs will be fetched.') 41 .requiredOption('-a, --app_name <type>', 'Name of the application that was profiled.'); 42 43 program.parse(process.argv); 44 45 const args = program.opts(); 46 this.moduleFile_ = args.module_file; 47 this.outputDir_ = args.output_dir; 48 this.appName_ = args.app_name; 49 50 if (!fs.existsSync(this.outputDir_)) { 51 console.log('Directory ', this.outputDir_, 'doesnt exist.'); 52 return false; 53 } 54 55 return true; 56 } 57 58 private isEnvVarsSet(): boolean { 59 let isEnvVarSet: boolean = true; 60 61 if (!process.env.HDC_PATH && !process.env.HDC_IP) { 62 console.log('Error in isEnvVarsSet: Please export HDC_PATH or HDC_IP(with port).'); 63 isEnvVarSet = false; 64 } 65 66 return isEnvVarSet; 67 } 68 69 // file line format: <checksum path> 70 private parseModuleFile(): boolean { 71 const fileContent = fs.readFileSync(this.moduleFile_, 'utf-8'); 72 fileContent.split(/\r?\n/).forEach((line) => { 73 if (line) { 74 let splitParts: Array<string> = line.split(' '); 75 let checksum: number = Number(splitParts[0]); 76 let path: string = this.mapSandboxDirToPhysicalDir(splitParts[1]); 77 78 if (!this.pathChecksumMap_.has(path)) { 79 this.pathChecksumMap_.set(path, checksum); 80 } 81 } 82 }); 83 84 return !(this.pathChecksumMap_.size === 0); 85 } 86 87 // According to OHOS docs we need to do mapping in this way: 88 // /data/storage/el1/bundle ---> /data/app/el1/bundle/public/<PACKAGENAME> 89 private mapSandboxDirToPhysicalDir(path: string): string { 90 return path.replace(/\/data\/storage\/el1\/bundle/g, `/data/app/el1/bundle/public/${this.appName_}`); 91 } 92 93 public DoFetching(): boolean { 94 this.pathChecksumMap_.forEach((checksum: number, path: string) => { 95 if (!this.areLibrariesIdentical(path, checksum)) { 96 nodeCmd.runSync(`${this.hdc_} file recv ${path} ${this.outputDir_}`, (err, data, stderr) => console.log(data)); 97 } 98 }); 99 100 return true; 101 } 102 103 private areLibrariesIdentical(devicePath: string, deviceChecksum: number): boolean { 104 let hostFilepath : string = path.join(this.outputDir_, path.basename(devicePath)); 105 if (!fs.existsSync(hostFilepath)) { 106 return false; 107 } 108 109 let hostChecksum: number = this.getHostChecksumFromPandaFile(hostFilepath); 110 return deviceChecksum === hostChecksum; 111 } 112 113 private getHostChecksumFromPandaFile(hostFilepath: string): number { 114 let magicSize: number = 8; 115 let magicArraySize: number = 1 * magicSize; 116 let checksumOffset: number = magicArraySize; 117 118 let data = fs.readFileSync(hostFilepath); 119 120 // read checksum (uint32_t) as a little-endian 121 return data.readUInt32LE(checksumOffset); 122 } 123 124 private getHdc(): string | undefined { 125 if (process.env.HDC_IP) { 126 return 'hdc'.concat(' -s ', process.env.HDC_IP); 127 } else if (process.env.HDC_PATH) { 128 return process.env.HDC_PATH; 129 } 130 131 return undefined; 132 } 133 134 public fetchModulesFromDevice(): void { 135 if (!this.isEnvVarsSet()) { 136 console.log('Error: enviroment variables is not set'); 137 return; 138 } 139 140 this.hdc_ = this.getHdc(); 141 if (this.hdc_ === undefined) { 142 console.log('Error: cant get hdc.'); 143 return; 144 } 145 146 if (!this.parseCliArgs()) { 147 console.log('Error: error when parsing CLI args.'); 148 return; 149 } 150 151 if (!this.parseModuleFile()) { 152 console.log('Error: can not parse module file.'); 153 return; 154 } 155 156 if (!this.DoFetching()) { 157 console.log('Error: Can not fetch modules from device.'); 158 return; 159 } 160 } 161} 162 163const fetcher = new ModulesFetcher(); 164fetcher.fetchModulesFromDevice(); 165